@apocaliss92/nodelink-js 0.4.11 → 0.4.13
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 +24 -0
- package/dist/BaichuanVideoStream-HGPU2MZ3.js +7 -0
- package/dist/{DiagnosticsTools-RNIDFEJK.js → DiagnosticsTools-BQOWJ23L.js} +3 -2
- package/dist/DiagnosticsTools-BQOWJ23L.js.map +1 -0
- package/dist/{chunk-EDLMKBG2.js → chunk-2JNXKT3C.js} +151 -2733
- package/dist/chunk-2JNXKT3C.js.map +1 -0
- package/dist/chunk-C57QV7IL.js +2723 -0
- package/dist/chunk-C57QV7IL.js.map +1 -0
- package/dist/{chunk-HGQ53FB3.js → chunk-R5AJV73A.js} +1348 -47
- package/dist/chunk-R5AJV73A.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +1422 -12
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -2
- package/dist/cli/rtsp-server.js.map +1 -1
- package/dist/index.cjs +1929 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1051 -172
- package/dist/index.d.ts +965 -46
- package/dist/index.js +547 -85
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-EDLMKBG2.js.map +0 -1
- package/dist/chunk-HGQ53FB3.js.map +0 -1
- /package/dist/{DiagnosticsTools-RNIDFEJK.js.map → BaichuanVideoStream-HGPU2MZ3.js.map} +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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/Go2rtcTcpServer.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 // ALL iOS clients need HLS for reliable video clip playback.\n // iOS AVFoundation requires Content-Length + Range support for regular MP4,\n // but generating the full MP4 upfront takes too long (camera download + transcode).\n // HLS delivers segments progressively (~3-5s to first frame vs 25+ seconds).\n // Safari, AVPlayer, and InstalledApp all support HLS natively.\n needsHls: isIos,\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 {\n discoverReolinkDevices,\n type DiscoveredDevice,\n type DiscoveryOptions,\n} from \"./discovery\";\n\nexport interface AutodiscoveryClientOptions extends DiscoveryOptions {\n /** Interval between discovery scans in milliseconds (default: 120000 = 2 minutes) */\n scanIntervalMs?: number;\n /** Whether to start discovery automatically on construction (default: false) */\n autoStart?: boolean;\n /** Called when new (previously unseen) devices are discovered */\n onDeviceDiscovered?: (device: DiscoveredDevice) => void;\n /** Called when an existing device's info is updated (e.g. model filled in) */\n onDeviceUpdated?: (device: DiscoveredDevice) => void;\n}\n\n/**\n * Continuous discovery client for Reolink cameras on the network.\n * Runs periodic scans using all configured discovery methods and maintains\n * an always-up-to-date list of discovered devices.\n *\n * Supports callbacks for new/updated devices, making it ideal for plugins\n * that want to be notified as cameras appear (e.g. battery cameras waking up).\n *\n * @example\n * ```typescript\n * const client = new AutodiscoveryClient({\n * enableArpLookup: true,\n * enableOnvifDiscovery: true,\n * scanIntervalMs: 60_000,\n * autoStart: true,\n * onDeviceDiscovered: (device) => {\n * console.log(`New device: ${device.host} (${device.model})`);\n * },\n * });\n *\n * // Get the current list\n * const devices = client.getDiscoveredDevices();\n *\n * // Stop\n * client.stop();\n * ```\n */\nexport class AutodiscoveryClient {\n private readonly options: AutodiscoveryClientOptions & {\n scanIntervalMs: number;\n };\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 constructor(options: AutodiscoveryClientOptions = {}) {\n this.options = {\n ...options,\n scanIntervalMs: options.scanIntervalMs ?? 120_000,\n autoStart: options.autoStart ?? false,\n };\n\n if (this.options.autoStart) {\n this.start();\n }\n }\n\n /**\n * Start continuous discovery. If already running, does nothing.\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 // Run first scan immediately\n this.performScan();\n\n // Schedule subsequent scans\n this.scheduleNextScan();\n }\n\n /**\n * Stop continuous discovery. If not running, does nothing.\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 * Returns the current list of discovered devices, sorted by host IP.\n */\n getDiscoveredDevices(): DiscoveredDevice[] {\n return Array.from(this.discoveredDevices.values()).sort((a, b) =>\n a.host.localeCompare(b.host),\n );\n }\n\n /**\n * Returns the number of currently discovered devices.\n */\n getDeviceCount(): number {\n return this.discoveredDevices.size;\n }\n\n /**\n * Returns whether continuous discovery is currently running.\n */\n isActive(): boolean {\n return this.isRunning;\n }\n\n /**\n * Force an immediate scan (doesn't wait for the scheduled interval).\n * If a scan is already in progress, waits for it to complete.\n */\n async scanNow(): Promise<void> {\n if (this.currentScanPromise) {\n this.options.logger?.log?.(\n \"[Autodiscovery] Scan already in progress, waiting for completion...\",\n );\n await this.currentScanPromise;\n return;\n }\n\n await this.performScan();\n }\n\n /**\n * Remove a device from the discovered list.\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 * Clear all discovered devices.\n */\n clearDevices(): void {\n const count = this.discoveredDevices.size;\n this.discoveredDevices.clear();\n this.options.logger?.log?.(\n `[Autodiscovery] Removed ${count} device(s) from list`,\n );\n }\n\n private async performScan(): Promise<void> {\n const scanPromise = (async () => {\n try {\n this.options.logger?.log?.(\"[Autodiscovery] Starting scan...\");\n\n const discovered = await discoverReolinkDevices(this.options);\n\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 const updated = this.mergeDeviceInfo(existing, device);\n if (updated) {\n updatedDevices.push(updated);\n }\n } else {\n this.discoveredDevices.set(device.host, { ...device });\n newDevices.push(device);\n }\n }\n\n this.options.logger?.log?.(\n `[Autodiscovery] Scan completed: ${newDevices.length} new, ${updatedDevices.length} updated, total: ${this.discoveredDevices.size}`,\n );\n\n // Fire callbacks\n for (const device of newDevices) {\n this.options.logger?.log?.(\n `[Autodiscovery] NEW DEVICE - ${device.host} | ${device.model ?? \"unknown\"} | ${device.name ?? \"\"} | via ${device.discoveryMethod}`,\n );\n try {\n this.options.onDeviceDiscovered?.(device);\n } catch {\n // ignore callback errors\n }\n }\n for (const device of updatedDevices) {\n try {\n this.options.onDeviceUpdated?.(device);\n } catch {\n // ignore callback errors\n }\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n this.options.logger?.error?.(\n `[Autodiscovery] Error during scan: ${msg}`,\n );\n }\n })();\n\n this.currentScanPromise = scanPromise;\n await scanPromise;\n this.currentScanPromise = null;\n }\n\n private mergeDeviceInfo(\n existing: DiscoveredDevice,\n updated: DiscoveredDevice,\n ): DiscoveredDevice | null {\n let hasChanges = false;\n\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 (\n updated.supportsHttps !== undefined &&\n existing.supportsHttps !== updated.supportsHttps\n ) {\n existing.supportsHttps = updated.supportsHttps;\n hasChanges = true;\n }\n if (\n updated.httpAccessible !== undefined &&\n existing.httpAccessible !== updated.httpAccessible\n ) {\n existing.httpAccessible = updated.httpAccessible;\n hasChanges = true;\n }\n\n return hasChanges ? existing : null;\n }\n\n private scheduleNextScan(): void {\n if (!this.isRunning) return;\n\n this.scanTimer = setTimeout(() => {\n this.scanTimer = null;\n if (this.isRunning) {\n this.performScan().finally(() => {\n if (this.isRunning) {\n this.scheduleNextScan();\n }\n });\n }\n }, this.options.scanIntervalMs);\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 /** PIR sensitivity (typically 1..100). Camera-side field is\n * `sensiValue` in the cmd_id 212 response. */\n sensitive?: number;\n /** False-positive reduction toggle (`reduceFalseAlarm` on the\n * wire). 0/1. */\n reduceAlarm?: number;\n /** Cooldown between consecutive PIR triggers (seconds). */\n interval?: number;\n /** Firmware-advertised max value for `interval`. Drives the\n * upper bound of operator-facing sliders. */\n intervalMax?: 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 /** Raw XML response from GetEnc (cmd_id 56) for debugging stream availability */\n rawEncXml?: string | undefined;\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 /** Raw XML response from GetEnc (cmd_id 56) for debugging */\n rawXml?: string;\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 | \"battery\"\n | \"other\";\n\nexport interface ReolinkSimpleEvent {\n type: ReolinkSimpleEventType;\n channel: number;\n timestamp: number;\n /** Present when type === \"battery\" — pushed by the camera via cmdId 252. */\n battery?: Partial<BatteryInfo>;\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 /** True when device is a doorbell (can potentially support wireless chime) or has dingDong abilities explicitly detected. */\n hasWirelessChime: 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 /** Wireless chime IDs from GetDingDongList (cmd 484) */\n dingDongListIds?: number[];\n /** Wireless chime IDs from GetDingDongCfg (cmd 486) */\n dingDongCfgIds?: number[];\n /** Error message if chime discovery failed */\n wirelessChimeError?: string;\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 * Per-stream encoding entry inside `Compression`. Captured live on\n * E1-Zoom (TCP), Doorbell (UDP), Hub channel-0 (Argus 3E).\n *\n * Note: the camera-side field name is `frame`, NOT `frameRate`.\n * `videoEncType` is a numeric enum (`0` = h264, `1` = h265). The\n * `gop`/`encoderType`/`separateCfg` sub-blocks are firmware-dependent\n * (Hub-channel cameras carry them; standalone devices may not).\n */\nexport interface CompressionStream {\n audio?: number | undefined;\n resolutionName?: string | undefined;\n width?: number | undefined;\n height?: number | undefined;\n frame?: number | undefined;\n bitRate?: number | undefined;\n encoderProfile?: string | undefined;\n videoEncType?: number | undefined;\n gop?: { cur?: number; max?: number; min?: number } | undefined;\n encoderType?: string | undefined;\n [key: string]: unknown;\n}\n\n/**\n * Encoding configuration (getEnc response).\n * cmdId=56 (GetEnc) — payload is wrapped in `Compression`, not `Enc`.\n */\nexport interface EncConfig {\n body?: {\n Compression?: {\n channelId?: number | undefined;\n isNoTranslateFrame?: number | undefined;\n mainStream?: CompressionStream | undefined;\n subStream?: CompressionStream | undefined;\n thirdStream?: CompressionStream | undefined;\n separateCfg?: { encodeCfg?: number; [key: string]: unknown } | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * ISP / image input configuration. Both `getIsp` (cmdId=25) and\n * `getImage` (cmdId=26) return identical payloads on observed firmwares\n * — the underlying VideoInput + InputAdvanceCfg blocks. Different\n * cmdIds preserved for backwards compatibility.\n */\nexport interface IspConfig {\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 sharpen?: number | undefined;\n corridorAbility?: number | undefined;\n corridorMode?: string | undefined;\n [key: string]: unknown;\n };\n InputAdvanceCfg?: {\n channelId?: number | undefined;\n digitalChannel?: number | undefined;\n separateCfg?: { encType?: number; constantFrameRate?: number; [key: string]: unknown } | undefined;\n PowerLineFrequency?: { mode?: string; enable?: number; [key: string]: unknown } | undefined;\n Exposure?: {\n mode?: string;\n Gainctl?: { defMin?: number; defMax?: number; curMin?: number; curMax?: number };\n Shutterctl?: { defMin?: number; defMax?: number; curMin?: number; curMax?: number };\n shutterLevel?: string;\n gainLevel?: number;\n [key: string]: unknown;\n } | undefined;\n Scene?: {\n mode?: string;\n modeList?: string;\n Redgain?: { min?: number; max?: number; cur?: number };\n Bluegain?: { min?: number; max?: number; cur?: number };\n [key: string]: unknown;\n } | undefined;\n DayNight?: { mode?: string; IrcutMode?: string; Threshold?: string; [key: string]: unknown } | undefined;\n BLC?: { enable?: number; mode?: string; [key: string]: unknown } | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * IR / supplemental light state (`getIrLights`). Captured live on every\n * test camera — `state` is the operator-facing toggle, `lightState` is\n * the camera's own runtime status. `doorbellLightState` /\n * `doorbellAbility` only appear on doorbell models.\n */\nexport interface IrLightsConfig {\n body?: {\n LedState?: {\n channelId?: number | undefined;\n ledVersion?: number | undefined;\n IRLedBrightness?: number | undefined;\n state?: string | undefined;\n lightState?: string | undefined;\n doorbellLightState?: string | undefined;\n doorbellAbility?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Privacy mask configuration (`getMask`). The `Shelter` block always\n * contains `enable` + `maxNum` + `shelterList`; tracked-shelter sub-\n * blocks (`trackEnable`, `trackShelterList`) are PTZ-only and\n * conditional on the camera supporting motion-tracking. Hub-channel\n * cameras include `separateCfg` + `logicChannel`.\n */\nexport interface MaskConfig {\n body?: {\n Shelter?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n maxNum?: number | undefined;\n shelterList?: unknown;\n trackEnable?: number | undefined;\n maxTrackShelterNum?: number | undefined;\n trackShelterList?: unknown;\n separateCfg?: { shelter?: number; [key: string]: unknown } | undefined;\n logicChannel?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Audio noise reduction configuration (`getAudioNoise`).\n * cmdId=439. Note: the wire tag is lowercase `aiDenoise` (capital `V`\n * in `@_Version`). Mirrors the camera-side schema observed live.\n */\nexport interface AudioNoiseConfig {\n body?: {\n aiDenoise?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n level?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Auto-focus configuration (`getAutoFocus`). cmdId=224.\n *\n * The `disable` field is a 0/1 flag — `0` means autofocus is ENABLED.\n * Non-PTZ cameras may return an empty `{}` or 400 — callers should\n * narrow `body?.AutoFocus?.disable` defensively.\n */\nexport interface AutoFocusConfig {\n body?: {\n AutoFocus?: {\n channelId?: number | undefined;\n disable?: number | 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 /** Legacy format (some firmware versions) */\n item?: Array<{\n userName?: string | undefined;\n ip?: string | undefined;\n level?: number | undefined;\n [key: string]: unknown;\n }>;\n /** Current format (OnlineUser array) */\n OnlineUser?: Array<{\n userId?: number | undefined;\n sessionId?: number | undefined;\n userName?: string | undefined;\n userLevel?: number | undefined;\n ipAddress?: string | undefined;\n macAddress?: string | undefined;\n enableOutoffLine?: number | undefined;\n isOnline?: number | 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\n// --------------------\n// Chime / DingDong types\n// --------------------\n\n/** A paired wireless chime device as returned by GetDingDongList. */\nexport interface ChimeDevice {\n id: number;\n name: string;\n /** 0 = offline, 1 = online */\n netState: number;\n}\n\n/** Wireless chime parameters returned by DingDongOpt (option 2 / getParam). */\nexport interface ChimeParams {\n name?: string;\n /** Volume level (0-4 typical) */\n volLevel?: number;\n /** LED state: 0 = off, 1 = on */\n ledState?: number;\n}\n\n/** Per-event alarm config entry inside a chime's GetDingDongCfg response. */\nexport interface ChimeAlarmCfg {\n /** Whether this event type triggers the chime: 0 = off, 1 = on */\n valid: number;\n /** Ringtone / music ID */\n musicId: number;\n}\n\n/** Per-chime config from GetDingDongCfg. */\nexport interface ChimeCfg {\n /** Chime ring ID */\n id: number;\n /** Map of event type string → alarm config */\n type: Record<string, ChimeAlarmCfg>;\n}\n\n/** Hardwired (wired-in) chime state from GetDingDongCtrl / SetDingDongCtrl. */\nexport interface HardwiredChimeState {\n /** Chime type string (e.g. \"dingdong\", \"single\", \"dual\") */\n type: string;\n /** Whether the chime is enabled */\n enabled: boolean;\n /** Duration / timing value */\n time: number;\n}\n\n/** Wireless chime silent mode state from GetDingDongSilent / SetDingDongSilent. */\nexport interface WirelessChimeSilentState {\n /** The wireless chime device ID */\n id: number;\n /**\n * Silent mode duration in seconds.\n * 0 = not silenced (chime active), >0 = silenced for this many seconds.\n */\n time: number;\n /** Whether the chime is currently active (not silenced). Derived: time === 0 */\n active: boolean;\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(adtsFrame: Buffer): {\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 videoFramesSinceAbsUsChange = 0;\n private videoEstimatedFrameDeltaUs: 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 this.videoFramesSinceAbsUsChange = 0;\n this.videoEstimatedFrameDeltaUs = 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 lastAbsUs = this.videoLastAbsUs;\n const deltaUs = absUs - lastAbsUs;\n this.videoLastAbsUs = absUs;\n\n // Some camera models produce a coarse or repeated timestamp (e.g., same absUs for N frames\n // then jump by a larger delta). If we treat repeated timestamps as untrusted and use an EMA\n // of the coarse deltas, we end up advancing RTP timestamps too slowly (stutter/jitter).\n //\n // Heuristic:\n // - If absUs did not advance, use an estimated per-frame delta (learned from the next advance)\n // or fallback fps.\n // - If absUs advanced after a streak of repeats, assume the delta covers (streak+1) frames and\n // derive a per-frame delta.\n let effectiveDeltaUs: number;\n if (!Number.isFinite(deltaUs) || deltaUs < 0) {\n this.logVideoTiming(\n \"untrusted-delta\",\n `discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${lastAbsUs} avgDeltaUs=${this.videoAvgDeltaUs ?? \"n/a\"}); using fallback`,\n );\n this.videoFramesSinceAbsUsChange = 0;\n effectiveDeltaUs =\n this.videoEstimatedFrameDeltaUs ?? this.fallbackVideoDeltaUs;\n } else if (deltaUs === 0) {\n this.videoFramesSinceAbsUsChange++;\n // Keep the average stable; do NOT update EMA with zeros.\n effectiveDeltaUs =\n this.videoEstimatedFrameDeltaUs ?? this.fallbackVideoDeltaUs;\n } else if (deltaUs <= this.maxTrustedDeltaUs) {\n if (this.videoFramesSinceAbsUsChange > 0) {\n const framesCovered = this.videoFramesSinceAbsUsChange + 1;\n const perFrameDeltaUs = Math.max(\n 1,\n Math.round(deltaUs / framesCovered),\n );\n this.videoEstimatedFrameDeltaUs = perFrameDeltaUs;\n this.videoFramesSinceAbsUsChange = 0;\n\n const prevAvg = this.videoAvgDeltaUs ?? perFrameDeltaUs;\n this.videoAvgDeltaUs =\n prevAvg + (perFrameDeltaUs - prevAvg) * this.emaAlpha;\n effectiveDeltaUs = perFrameDeltaUs;\n } else {\n const prevAvg = this.videoAvgDeltaUs ?? deltaUs;\n this.videoAvgDeltaUs = prevAvg + (deltaUs - prevAvg) * this.emaAlpha;\n effectiveDeltaUs = deltaUs;\n }\n } else {\n // Large forward jump: do not incorporate into EMA.\n this.logVideoTiming(\n \"untrusted-delta\",\n `discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${lastAbsUs} avgDeltaUs=${this.videoAvgDeltaUs ?? \"n/a\"} sameCount=${this.videoFramesSinceAbsUsChange}); using fallback`,\n );\n this.videoFramesSinceAbsUsChange = 0;\n effectiveDeltaUs =\n this.videoEstimatedFrameDeltaUs ?? 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 sharedProcessId =\n typeof process !== \"undefined\" && typeof process.pid === \"number\"\n ? process.pid\n : \"n/a\";\n const sharedInstanceId = (() => {\n try {\n const g = globalThis as any;\n const k = \"__nodelink_rfc4571_shared_instance_id__\";\n if (!g[k]) {\n g[k] = Math.random().toString(16).slice(2);\n }\n return String(g[k]);\n } catch {\n return \"n/a\";\n }\n })();\n\n const sharedLog = (message: string) => {\n try {\n const logger: any = options.logger;\n const prefix = `[native-rfc4571 shared pid=${sharedProcessId} inst=${sharedInstanceId}]`;\n // Scrypted frequently suppresses debug logs; prefer info/log so traces are visible.\n if (logger?.info) logger.info(`${prefix} ${message}`);\n else if (logger?.log) logger.log(`${prefix} ${message}`);\n else if (logger?.debug) logger.debug(`${prefix} ${message}`);\n } catch {\n // ignore\n }\n };\n\n // For dedicated sockets (deviceId), idleTeardownMs MUST NOT be 0.\n // If the RFC4571 server never tears down, the dedicated Baichuan session is never\n // released and the device accumulates active sessions over time.\n const normalizeDedicatedIdleTeardownMs = (ms: number | undefined): number => {\n if (typeof ms === \"number\" && Number.isFinite(ms) && ms > 0) return ms;\n return 15_000;\n };\n\n // When deviceId is provided, callers may request the *same* native stream multiple\n // times using different external identifiers. That can lead to duplicate RFC4571\n // servers and therefore duplicate dedicated Baichuan sessions.\n //\n // To prevent this, we share a single RFC4571 server per stable stream identity.\n // Each caller gets a ref-counted handle; the underlying server is closed only when\n // all handles are released.\n const sharedKey = buildSharedRfc4571Key(options);\n if (!sharedKey) {\n sharedLog(\n \"disabled (no deviceId); using unshared RFC4571 server for this request\",\n );\n return await createRfc4571TcpServerInternal(options);\n }\n\n sharedLog(\n `request key=${formatSharedKeyForLog(sharedKey)} (requestedId=${options.requestedId ?? \"n/a\"})`,\n );\n\n const existing = sharedRfc4571Servers.get(sharedKey);\n if (existing && existing.server.server.listening) {\n sharedLog(\n `reuse existing host=${existing.server.host} port=${existing.server.port} refCount=${existing.refCount}`,\n );\n return acquireSharedRfc4571Handle(sharedKey, existing, sharedLog);\n }\n if (existing) {\n // Stale entry.\n sharedLog(\n `stale entry found; dropping (listening=${existing.server.server.listening})`,\n );\n sharedRfc4571Servers.delete(sharedKey);\n }\n\n const inFlight = sharedRfc4571Creates.get(sharedKey);\n if (inFlight) {\n sharedLog(\"awaiting in-flight create\");\n await inFlight;\n const created = sharedRfc4571Servers.get(sharedKey);\n if (created) {\n sharedLog(\n `acquiring after in-flight create host=${created.server.host} port=${created.server.port} refCount=${created.refCount}`,\n );\n return acquireSharedRfc4571Handle(sharedKey, created, sharedLog);\n }\n // Fallback: avoid deadlocks.\n sharedLog(\n \"in-flight create completed but no entry found; falling back to unshared\",\n );\n return await createRfc4571TcpServerInternal(options);\n }\n\n const createPromise = (async () => {\n const idleTeardownMs = normalizeDedicatedIdleTeardownMs(\n options.idleTeardownMs,\n );\n sharedLog(\n `creating new shared RFC4571 server (idleTeardownMs=${idleTeardownMs})`,\n );\n const server = await createRfc4571TcpServerInternal({\n ...options,\n // Force a sane idle teardown for dedicated sessions so sockets are released.\n idleTeardownMs,\n });\n\n const entry: SharedRfc4571Entry = {\n server,\n refCount: 0,\n };\n sharedRfc4571Servers.set(sharedKey, entry);\n sharedLog(`created host=${server.host} port=${server.port}`);\n\n // Best-effort cleanup if the underlying server tears down itself (errors, watchdog).\n server.server.once(\"close\", () => {\n const current = sharedRfc4571Servers.get(sharedKey);\n if (current?.server === server) {\n sharedLog(\"underlying server closed; evicting shared cache entry\");\n sharedRfc4571Servers.delete(sharedKey);\n }\n });\n })().finally(() => {\n sharedRfc4571Creates.delete(sharedKey);\n });\n\n sharedRfc4571Creates.set(sharedKey, createPromise);\n await createPromise;\n\n const created = sharedRfc4571Servers.get(sharedKey);\n if (created) return acquireSharedRfc4571Handle(sharedKey, created, sharedLog);\n return await createRfc4571TcpServerInternal(options);\n}\n\ninterface SharedRfc4571Entry {\n server: Rfc4571TcpServer;\n refCount: number;\n}\n\nconst sharedRfc4571Servers = new Map<string, SharedRfc4571Entry>();\nconst sharedRfc4571Creates = new Map<string, Promise<void>>();\n\nconst buildSharedRfc4571Key = (\n options: Rfc4571TcpServerOptions,\n): string | undefined => {\n // Only share when deviceId is provided (dedicated sockets).\n if (!options.deviceId) return;\n\n // This key intentionally ignores external identifiers like requestedId.\n // It captures the stream identity that maps to a single Baichuan live stream.\n const channelKey =\n options.channel === undefined ? \"composite\" : String(options.channel);\n const variantKey = options.variant ?? \"default\";\n\n // IMPORTANT: Do NOT include RFC4571 client auth credentials in the key.\n // Callers may generate a new username/password on each request. If we include them,\n // the key becomes unstable and sharing never happens.\n // The shared server will return its own username/password to the caller.\n const requireAuth = Boolean(options.requireAuth);\n\n // Include payload types because they affect SDP.\n const videoPayloadType = options.videoPayloadType ?? 96;\n const audioPayloadType = options.audioPayloadType ?? 97;\n\n return [\n \"rfc4571\",\n `device:${options.deviceId}`,\n `ch:${channelKey}`,\n `profile:${options.profile}`,\n `variant:${variantKey}`,\n `auth:${requireAuth ? \"1\" : \"0\"}`,\n `vpt:${videoPayloadType}`,\n `apt:${audioPayloadType}`,\n ].join(\"|\");\n};\n\nconst formatSharedKeyForLog = (key: string): string => {\n // Redact any unexpected secrets if the format changes in the future.\n // Current key format doesn't include passwords, but keep this defensive.\n return key.replace(/\\|pass:[^|]*/g, \"|pass:<redacted>\");\n};\n\nconst acquireSharedRfc4571Handle = (\n key: string,\n entry: SharedRfc4571Entry,\n sharedLog?: (message: string) => void,\n): Rfc4571TcpServer => {\n entry.refCount++;\n sharedLog?.(`acquire handle key=${key} refCount=${entry.refCount}`);\n\n let released = false;\n const serverRef = entry.server;\n\n const close = async (reason?: unknown): Promise<void> => {\n if (released) return;\n released = true;\n\n const current = sharedRfc4571Servers.get(key);\n // If the server was already replaced/removed, do not interfere.\n if (!current || current.server !== serverRef) return;\n\n current.refCount = Math.max(0, current.refCount - 1);\n sharedLog?.(\n `release handle key=${key} refCount=${current.refCount} reason=${String(\n (reason as any)?.message ?? reason ?? \"n/a\",\n )}`,\n );\n if (current.refCount > 0) return;\n\n sharedRfc4571Servers.delete(key);\n sharedLog?.(\"refCount reached 0; closing underlying server\");\n await serverRef.close(reason ?? \"shared handle released\");\n };\n\n // Provide a handle that shares the underlying server but has an independent close().\n return {\n ...serverRef,\n close,\n };\n};\n\nasync function createRfc4571TcpServerInternal(\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 // If the camera already rejected the stream request during start()\n // (e.g. response_code 400), the error was stashed because no listener\n // existed yet. Consume it now to fail fast instead of waiting for the\n // full keyframe timeout.\n const pendingErr = (\n videoStream as BaichuanVideoStream\n ).consumePendingStartupError?.();\n if (pendingErr) {\n cleanup();\n reject(pendingErr);\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 // Release dedicated session if one was created (prevents session leak).\n if (dedicatedSession) {\n try {\n await dedicatedSession.release();\n } catch {\n // ignore\n }\n }\n\n // When a dedicated session was used, the baseApi is shared with other streams\n // (events, main/sub). Closing or idle-disconnecting it here would cascade-kill\n // unrelated streams. Only release the dedicated session (above) and leave the\n // shared API untouched.\n if (!dedicatedSession) {\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\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 // When a dedicated session was used, the baseApi is shared with other streams\n // (events, main/sub). Closing it here would cascade-kill unrelated streams.\n // Only close APIs when no dedicated session was used (API is owned by this server).\n if (closeApiOnTeardown && !dedicatedSession) {\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 * Go2rtcTcpServer — Lightweight TCP server that feeds raw Annex-B video\n * (H.264 / H.265) and ADTS AAC audio frames to go2rtc via a plain TCP\n * connection.\n *\n * go2rtc configuration example:\n * streams:\n * camera_main: tcp://127.0.0.1:{port}\n *\n * go2rtc auto-detects the codec from the bitstream (SPS/PPS/VPS NALUs).\n *\n * Lifecycle:\n * 1. Server starts listening immediately on the configured port.\n * 2. When the first TCP client connects the native camera stream is started.\n * 3. Frames are fan-out to every connected client (go2rtc typically opens\n * one connection, but multiple are supported).\n * 4. When the last client disconnects a grace period runs before stopping\n * the native stream (avoids thrashing on rapid reconnects).\n * 5. A prebuffer ring keeps the last few seconds of frames so new clients\n * get IDR-aligned fast startup without waiting for the next keyframe.\n */\n\nimport { EventEmitter } from \"node:events\";\nimport * as net from \"node:net\";\nimport type { StreamProfile } from \"../../reolink/baichuan/types\";\nimport type { ReolinkBaichuanApi } from \"../../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../../reolink/baichuan/types\";\nimport type { Logger } from \"../../debug/DebugConfig\";\nimport { createNativeStream } from \"../../rfc/helpers\";\nimport { convertToAnnexB as convertH264ToAnnexB } from \"./H264Converter\";\nimport {\n convertToAnnexB as convertH265ToAnnexB,\n isH265Irap,\n splitAnnexBToNalPayloads,\n} from \"./H265Converter\";\nimport { MpegTsMuxer } from \"./MpegTsMuxer\";\n\n// ---------------------------------------------------------------------------\n// Bounded queue (same as BaichuanRtspServer, kept local to avoid coupling)\n// ---------------------------------------------------------------------------\n\nclass AsyncBoundedQueue<T> {\n private readonly maxItems: number;\n private readonly queue: T[] = [];\n private waiting:\n | { resolve: (r: IteratorResult<T>) => void }\n | undefined;\n private closed = false;\n\n constructor(maxItems: number) {\n this.maxItems = Math.max(1, maxItems | 0);\n }\n\n push(item: T): void {\n if (this.closed) return;\n if (this.waiting) {\n const { resolve } = this.waiting;\n this.waiting = undefined;\n resolve({ value: item, done: false });\n return;\n }\n this.queue.push(item);\n if (this.queue.length > this.maxItems) {\n this.queue.splice(0, this.queue.length - this.maxItems);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n if (this.waiting) {\n const { resolve } = this.waiting;\n this.waiting = undefined;\n resolve({ value: undefined as any, done: true });\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n if (this.closed) return { value: undefined as any, done: true };\n const item = this.queue.shift();\n if (item !== undefined) return { value: item, done: false };\n return await new Promise<IteratorResult<T>>((resolve) => {\n this.waiting = { resolve };\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// NativeStreamFanout (same pattern as BaichuanRtspServer)\n// ---------------------------------------------------------------------------\n\ntype NativeFrame = {\n audio: boolean;\n data: Buffer;\n codec: string | null;\n sampleRate: number | null;\n microseconds: number | null;\n videoType?: \"H264\" | \"H265\";\n isKeyframe?: boolean;\n};\n\ntype FanoutOptions = {\n maxQueueItems: number;\n createSource: () => AsyncGenerator<NativeFrame, void, unknown>;\n onFrame?: (frame: NativeFrame) => void;\n onError?: (error: unknown) => void;\n onEnd?: () => void;\n};\n\nclass NativeStreamFanout {\n private readonly opts: FanoutOptions;\n private readonly queues = new Map<string, AsyncBoundedQueue<NativeFrame>>();\n private source: AsyncGenerator<NativeFrame, void, unknown> | null = null;\n private running = false;\n private pumpPromise: Promise<void> | null = null;\n\n constructor(opts: FanoutOptions) {\n this.opts = opts;\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n this.source = this.opts.createSource();\n\n this.pumpPromise = (async () => {\n try {\n for await (const frame of this.source!) {\n try {\n this.opts.onFrame?.(frame);\n } catch {\n // ignore observer errors\n }\n for (const q of this.queues.values()) {\n q.push(frame);\n }\n }\n } catch (e) {\n this.opts.onError?.(e);\n } finally {\n for (const q of this.queues.values()) q.close();\n this.queues.clear();\n this.running = false;\n this.opts.onEnd?.();\n }\n })();\n }\n\n subscribe(id: string): AsyncGenerator<NativeFrame, void, unknown> {\n const q = new AsyncBoundedQueue<NativeFrame>(this.opts.maxQueueItems);\n this.queues.set(id, q);\n const self = this;\n return (async function* () {\n try {\n while (true) {\n const r = await q.next();\n if (r.done) return;\n yield r.value;\n }\n } finally {\n q.close();\n self.queues.delete(id);\n }\n })();\n }\n\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n const src = this.source;\n this.source = null;\n for (const q of this.queues.values()) q.close();\n this.queues.clear();\n try {\n await src?.return(undefined as any);\n } catch {\n // ignore\n }\n try {\n await this.pumpPromise;\n } catch {\n // ignore\n }\n this.pumpPromise = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Prebuffer entry\n// ---------------------------------------------------------------------------\n\ninterface PrebufferEntry {\n /** Annex-B (video) or raw ADTS (audio) — per-client MPEG-TS muxing happens in feedClient. */\n data: Buffer;\n /** Wallclock ms (for prebuffer trimming). */\n time: number;\n isKeyframe: boolean;\n audio: boolean;\n /** Camera presentation timestamp in microseconds (for MPEG-TS PTS). */\n pts: number;\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface Go2rtcTcpServerOptions {\n /** API instance (required). */\n api: ReolinkBaichuanApi;\n /** Channel number (required). */\n channel: number;\n /** Stream profile (required). */\n profile: StreamProfile;\n /** TrackMix tele/autotrack variant. */\n variant?: NativeVideoStreamVariant;\n /** Host to listen on (default: \"127.0.0.1\"). */\n listenHost?: string;\n /** Port to listen on (default: 0 = OS picks). */\n listenPort?: number;\n /** Logger. */\n logger?: Logger;\n /**\n * External identifier for dedicated socket session.\n * When provided, a dedicated BaichuanClient is created for the stream,\n * isolating it from other streams on the shared socket.\n */\n deviceId?: string;\n /** Grace period in ms before stopping native stream after last client disconnects (default: 30 000). */\n gracePeriodMs?: number;\n /** Maximum prebuffer window in ms (default: 3 000). */\n prebufferMs?: number;\n /** Maximum write buffer per client before dropping the connection (default: 100 MB). */\n maxBufferBytes?: number;\n /**\n * Stream inactivity timeout in ms. If no frames arrive from the native\n * stream for this duration, the stream is considered dead and will be\n * torn down + restarted (similar to go2rtc's per-packet read deadline).\n * Default: 15 000 (15s). Set to 0 to disable.\n */\n streamTimeoutMs?: number;\n /**\n * When true, the native camera stream is started immediately on start()\n * rather than waiting for the first TCP client. This ensures frames are\n * already flowing (and the prebuffer is warm) when go2rtc connects.\n * Default: true.\n */\n prestartStream?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Go2rtcTcpServer\n// ---------------------------------------------------------------------------\n\nexport class Go2rtcTcpServer extends EventEmitter<{\n client: [string];\n clientDisconnected: [string];\n error: [Error];\n close: [];\n listening: [{ host: string; port: number }];\n}> {\n private readonly api: ReolinkBaichuanApi;\n private readonly channel: number;\n private readonly profile: StreamProfile;\n private readonly variant: NativeVideoStreamVariant;\n private readonly listenHost: string;\n private readonly listenPort: number;\n private readonly logger: Logger;\n private readonly deviceId: string | undefined;\n private readonly gracePeriodMs: number;\n private readonly prebufferMaxMs: number;\n private readonly maxBufferBytes: number;\n private readonly streamTimeoutMs: number;\n private readonly prestartStream: boolean;\n\n private active = false;\n private server: net.Server | undefined;\n private resolvedPort: number | undefined;\n\n // Native stream\n private nativeFanout: NativeStreamFanout | null = null;\n private nativeStreamActive = false;\n private dedicatedSessionRelease: (() => Promise<void>) | undefined;\n private detectedVideoType: \"H264\" | \"H265\" | undefined;\n\n // Client tracking\n private connectedClients = new Set<string>();\n private clientSockets = new Map<string, net.Socket>();\n private stopGraceTimer: ReturnType<typeof setTimeout> | undefined;\n\n // Stream health monitoring\n private lastFrameAt = 0;\n private streamHealthTimer: ReturnType<typeof setInterval> | undefined;\n private totalFramesReceived = 0;\n private totalVideoFramesWritten = 0;\n\n // Prebuffer\n private prebuffer: PrebufferEntry[] = [];\n\n // Audio metadata — populated on first valid ADTS AAC frame.\n // Exposed via getAudioInfo() for the stream-diagnostics feature.\n private audioInfo: {\n codec: \"aac-adts\";\n sampleRate: number;\n channels: number;\n configHex: string;\n } | null = null;\n\n constructor(options: Go2rtcTcpServerOptions) {\n super();\n this.api = options.api;\n this.channel = options.channel;\n this.profile = options.profile;\n this.variant = options.variant ?? \"default\";\n this.listenHost = options.listenHost ?? \"127.0.0.1\";\n this.listenPort = options.listenPort ?? 0;\n this.logger = options.logger ?? console;\n this.deviceId = options.deviceId;\n this.gracePeriodMs = options.gracePeriodMs ?? 30_000;\n this.prebufferMaxMs = options.prebufferMs ?? 3_000;\n this.maxBufferBytes = options.maxBufferBytes ?? 100_000_000;\n this.streamTimeoutMs = options.streamTimeoutMs ?? 15_000;\n this.prestartStream = options.prestartStream ?? true;\n }\n\n // -----------------------------------------------------------------------\n // Public API\n // -----------------------------------------------------------------------\n\n /** Start listening. Resolves once the TCP server is bound. */\n async start(): Promise<void> {\n if (this.active) return;\n this.active = true;\n\n this.server = net.createServer((socket) => this.handleClient(socket));\n this.server.on(\"error\", (err) => {\n this.logger.error?.(`[Go2rtcTcpServer] server error: ${err.message}`);\n this.emit(\"error\", err);\n });\n\n await new Promise<void>((resolve, reject) => {\n this.server!.listen(this.listenPort, this.listenHost, () => {\n const addr = this.server!.address() as net.AddressInfo;\n this.resolvedPort = addr.port;\n this.logger.info?.(\n `[Go2rtcTcpServer] listening on ${addr.address}:${addr.port} channel=${this.channel} profile=${this.profile}`,\n );\n this.emit(\"listening\", { host: addr.address, port: addr.port });\n resolve();\n });\n this.server!.once(\"error\", reject);\n });\n\n // Pre-start the native camera stream so the prebuffer is warm when go2rtc\n // connects. Without this, go2rtc connects → sees no data → disconnects.\n if (this.prestartStream) {\n this.logger.info?.(\n `[Go2rtcTcpServer] pre-starting native stream channel=${this.channel} profile=${this.profile}`,\n );\n this.startNativeStream();\n }\n }\n\n /** Stop the server and all active streams. */\n async stop(): Promise<void> {\n if (!this.active) return;\n this.active = false;\n clearTimeout(this.stopGraceTimer);\n this.stopStreamHealthMonitor();\n\n // Close all client sockets\n for (const [id, sock] of this.clientSockets) {\n sock.destroy();\n this.connectedClients.delete(id);\n }\n this.clientSockets.clear();\n\n // Stop native stream\n await this.stopNativeStream();\n\n // Close TCP server\n if (this.server) {\n await new Promise<void>((resolve) => {\n this.server!.close(() => resolve());\n });\n this.server = undefined;\n }\n\n this.prebuffer = [];\n this.resolvedPort = undefined;\n this.emit(\"close\");\n }\n\n /** The actual port the server is listening on (available after start()). */\n get port(): number | undefined {\n return this.resolvedPort;\n }\n\n /** The go2rtc-compatible source URL. */\n get go2rtcSourceUrl(): string | undefined {\n if (this.resolvedPort == null) return undefined;\n return `tcp://127.0.0.1:${this.resolvedPort}`;\n }\n\n /** Number of currently connected clients. */\n get clientCount(): number {\n return this.connectedClients.size;\n }\n\n // -----------------------------------------------------------------------\n // Diagnostic subscription API (implements DiagnosticStreamServer)\n //\n // Matches the shape of BaichuanRtspServer's diagnostic API so the\n // stream-diagnostic feature in the Manager app can drive either backend\n // with identical code.\n // -----------------------------------------------------------------------\n\n /**\n * Subscribe to the raw native stream for diagnostic purposes.\n * The subscriber receives the same frames the MPEG-TS muxer consumes\n * (pre-muxing). Counts as a \"consumer\" so the native stream is kept alive\n * for the lifetime of the subscription. If the stream is not already\n * running (battery camera, prestart=false), this starts it.\n */\n async subscribeDiagnostic(\n id: string,\n ): Promise<AsyncGenerator<NativeFrame, void, unknown>> {\n this.connectedClients.add(`diag:${id}`);\n if (!this.nativeStreamActive) {\n await this.startNativeStream();\n }\n if (!this.nativeFanout) {\n this.connectedClients.delete(`diag:${id}`);\n throw new Error(\n \"Go2rtcTcpServer: native stream failed to start — cannot subscribe diagnostic\",\n );\n }\n return this.nativeFanout.subscribe(`diag:${id}`);\n }\n\n /** Unsubscribe a diagnostic session and release its consumer slot. */\n unsubscribeDiagnostic(id: string): void {\n this.removeClient(`diag:${id}`, \"diagnostic unsubscribe\");\n }\n\n /**\n * Returns ADTS AAC audio metadata detected from the native stream, or\n * null if no audio frame has been observed yet (e.g. video-only cameras\n * or before the first audio packet arrives).\n */\n getAudioInfo(): {\n codec: \"aac-adts\";\n sampleRate: number;\n channels: number;\n configHex: string;\n } | null {\n return this.audioInfo;\n }\n\n // -----------------------------------------------------------------------\n // Client handling\n // -----------------------------------------------------------------------\n\n private handleClient(socket: net.Socket): void {\n const clientId = `${socket.remoteAddress}:${socket.remotePort}`;\n socket.setNoDelay(true);\n\n this.connectedClients.add(clientId);\n this.clientSockets.set(clientId, socket);\n this.logger.info?.(\n `[Go2rtcTcpServer] client connected id=${clientId} total=${this.connectedClients.size}`,\n );\n this.emit(\"client\", clientId);\n\n // Cancel any pending grace-period stop\n if (this.stopGraceTimer) {\n clearTimeout(this.stopGraceTimer);\n this.stopGraceTimer = undefined;\n }\n\n // Ensure native stream is running. Every TCP connection from go2rtc\n // represents a real downstream consumer (go2rtc 1.9.x opens the producer\n // connection on-demand when a client subscribes to the stream — it does\n // not probe idle sources). Starting the native stream here will wake a\n // sleeping battery camera.\n if (!this.nativeStreamActive) {\n this.startNativeStream();\n }\n\n // Feed this client\n this.feedClient(clientId, socket).catch((err) => {\n this.logger.warn?.(\n `[Go2rtcTcpServer] feedClient error id=${clientId}: ${err}`,\n );\n });\n\n const cleanup = (reason?: string) => {\n this.removeClient(clientId, reason);\n socket.destroy();\n };\n socket.on(\"error\", (err) => cleanup(`error: ${err.message}`));\n socket.on(\"close\", (hadError) => cleanup(hadError ? \"close (with error)\" : \"close (clean)\"));\n }\n\n private async feedClient(clientId: string, socket: net.Socket): Promise<void> {\n // Wait for the fanout to be ready — for on-demand (battery) cameras this\n // may take several seconds while the camera wakes up and the stream starts.\n const fanoutDeadline = Date.now() + 30_000;\n while (this.active && !this.nativeFanout) {\n if (socket.destroyed) return;\n if (Date.now() > fanoutDeadline) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] fanout not ready after 30s, dropping client ${clientId}`,\n );\n return;\n }\n await new Promise((r) => setTimeout(r, 100));\n }\n if (!this.active || !this.nativeFanout) return;\n\n const subscription = this.nativeFanout.subscribe(clientId);\n\n // Per-client MPEG-TS muxer — created on first video keyframe so we know\n // the video codec (H264/H265) and the muxer emits the correct PMT stream type.\n let muxer: MpegTsMuxer | null = null;\n\n // ---- Prebuffer replay: send from last video IDR onwards ----\n const prebufferSnap = this.prebuffer.slice();\n let lastIdrIdx = -1;\n for (let i = prebufferSnap.length - 1; i >= 0; i--) {\n if (prebufferSnap[i]!.isKeyframe) {\n lastIdrIdx = i;\n break;\n }\n }\n\n if (lastIdrIdx >= 0) {\n const replay = prebufferSnap.slice(lastIdrIdx);\n this.logger.info?.(\n `[Go2rtcTcpServer] prebuffer replay client=${clientId} frames=${replay.length}`,\n );\n // Initialize muxer from the first (IDR) video frame in the prebuffer\n if (!muxer) {\n muxer = new MpegTsMuxer({\n videoType: this.detectedVideoType ?? \"H264\",\n includeAudio: true,\n });\n }\n for (const entry of replay) {\n if (socket.destroyed) return;\n let ts: Buffer;\n if (!entry.audio) {\n ts = muxer.muxVideo(entry.data, entry.pts, entry.isKeyframe);\n } else {\n ts = muxer.muxAudio(entry.data, entry.pts);\n }\n if (ts.length > 0) socket.write(ts);\n }\n }\n\n // ---- Live frames ----\n let seenKeyframe = lastIdrIdx >= 0;\n let liveFrameCount = 0;\n let liveVideoWritten = 0;\n let lastLogAt = Date.now();\n try {\n this.logger.info?.(\n `[Go2rtcTcpServer] entering live loop client=${clientId} seenKeyframe=${seenKeyframe}`,\n );\n for await (const frame of subscription) {\n if (socket.destroyed || !this.active) {\n this.logger.info?.(\n `[Go2rtcTcpServer] live loop exit client=${clientId} destroyed=${socket.destroyed} active=${this.active}`,\n );\n break;\n }\n\n liveFrameCount++;\n\n if (frame.audio) {\n // Audio: only write after muxer is ready (i.e. after first video keyframe)\n if (muxer) {\n const pts = frame.microseconds ?? Date.now() * 1000;\n const ts = muxer.muxAudio(frame.data, pts);\n if (ts.length > 0) socket.write(ts);\n }\n continue;\n }\n\n // Video frame — convert to Annex-B\n const annexB = this.convertVideoFrame(frame);\n if (!annexB) continue;\n\n const isKf = this.isAnnexBKeyframe(annexB, frame.videoType);\n\n // Gate on first keyframe so go2rtc doesn't get orphan P-frames\n if (!seenKeyframe) {\n if (!isKf) continue;\n seenKeyframe = true;\n this.logger.info?.(\n `[Go2rtcTcpServer] first live keyframe client=${clientId} after ${liveFrameCount} frames`,\n );\n // Initialize muxer now that we know the codec from the actual frame\n if (!muxer) {\n muxer = new MpegTsMuxer({\n videoType: frame.videoType ?? this.detectedVideoType ?? \"H264\",\n includeAudio: true,\n });\n }\n }\n\n const pts = frame.microseconds ?? Date.now() * 1000;\n const ts = muxer!.muxVideo(annexB, pts, isKf);\n socket.write(ts);\n\n liveVideoWritten++;\n this.totalVideoFramesWritten++;\n\n // Periodic log every 10 seconds\n if (Date.now() - lastLogAt > 10_000) {\n this.logger.info?.(\n `[Go2rtcTcpServer] live stats client=${clientId} received=${liveFrameCount} written=${liveVideoWritten} bufLen=${socket.writableLength}`,\n );\n lastLogAt = Date.now();\n }\n\n // Backpressure: drop client if buffer grows too large\n if (socket.writableLength > this.maxBufferBytes) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] buffer overflow (${socket.writableLength} bytes), dropping client ${clientId}`,\n );\n socket.destroy();\n break;\n }\n }\n this.logger.info?.(\n `[Go2rtcTcpServer] live loop ended naturally client=${clientId} received=${liveFrameCount} written=${liveVideoWritten}`,\n );\n } finally {\n await subscription.return(undefined as any).catch(() => {});\n }\n }\n\n // -----------------------------------------------------------------------\n // Frame conversion\n // -----------------------------------------------------------------------\n\n /**\n * Convert a native video frame to Annex-B.\n * Returns null for audio frames (handled separately by muxAudio).\n */\n private convertVideoFrame(frame: NativeFrame): Buffer | null {\n if (frame.audio) return null;\n if (frame.data.length === 0) return null;\n try {\n if (frame.videoType === \"H264\") {\n return convertH264ToAnnexB(frame.data);\n }\n if (frame.videoType === \"H265\") {\n return convertH265ToAnnexB(frame.data);\n }\n } catch {\n // ignore conversion errors\n }\n // Unknown codec — pass raw data through\n return frame.data;\n }\n\n /** Check if an Annex-B buffer contains a keyframe (IDR for H.264, IRAP for H.265). */\n private isAnnexBKeyframe(\n annexB: Buffer,\n videoType?: \"H264\" | \"H265\",\n ): boolean {\n try {\n if (videoType === \"H264\") {\n const nals = Go2rtcTcpServer.splitAnnexBNals(annexB);\n return nals.some((n) => n.length >= 1 && (n[0]! & 0x1f) === 5);\n }\n if (videoType === \"H265\") {\n const nals = splitAnnexBToNalPayloads(annexB);\n return nals.some(\n (n) => n.length >= 2 && isH265Irap((n[0]! >> 1) & 0x3f),\n );\n }\n } catch {\n // ignore\n }\n return false;\n }\n\n /** Split Annex-B byte stream into individual NAL units. */\n private static splitAnnexBNals(buf: Buffer): Buffer[] {\n const nals: Buffer[] = [];\n let i = 0;\n while (i < buf.length) {\n // Find start code (0x000001 or 0x00000001)\n if (\n i + 2 < buf.length &&\n buf[i] === 0 &&\n buf[i + 1] === 0\n ) {\n let scLen: number;\n if (buf[i + 2] === 1) {\n scLen = 3;\n } else if (i + 3 < buf.length && buf[i + 2] === 0 && buf[i + 3] === 1) {\n scLen = 4;\n } else {\n i++;\n continue;\n }\n // Find next start code\n const nalStart = i + scLen;\n let nalEnd = buf.length;\n for (let j = nalStart; j < buf.length - 2; j++) {\n if (\n buf[j] === 0 &&\n buf[j + 1] === 0 &&\n (buf[j + 2] === 1 || (j + 3 < buf.length && buf[j + 2] === 0 && buf[j + 3] === 1))\n ) {\n nalEnd = j;\n break;\n }\n }\n if (nalEnd > nalStart) {\n nals.push(buf.subarray(nalStart, nalEnd));\n }\n i = nalEnd;\n } else {\n i++;\n }\n }\n return nals;\n }\n\n // -----------------------------------------------------------------------\n // ADTS AAC parsing (used for audio metadata exposed via getAudioInfo)\n // -----------------------------------------------------------------------\n\n /** True if `b` starts with an ADTS AAC syncword (0xFFF). */\n private static isAdtsAacFrame(b: Buffer): boolean {\n return b.length >= 2 && b[0] === 0xff && (b[1]! & 0xf0) === 0xf0;\n }\n\n /**\n * Parse an ADTS header into {sampleRate, channels, AudioSpecificConfig hex}.\n * Returns null when the buffer is not a valid ADTS frame.\n */\n private static parseAdtsSamplingInfo(\n b: Buffer,\n ): { sampleRate: number; channels: number; configHex: string } | null {\n if (b.length < 7) return null;\n if (!Go2rtcTcpServer.isAdtsAacFrame(b)) return null;\n\n const samplingIndex = (b[2]! >> 2) & 0x0f;\n const sampleRates = [\n 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000,\n 11025, 8000, 7350,\n ];\n const sampleRate = sampleRates[samplingIndex] ?? null;\n if (!sampleRate) return null;\n\n const channelConfig = ((b[2]! & 0x01) << 2) | ((b[3]! >> 6) & 0x03);\n const channels = channelConfig === 0 ? 1 : channelConfig;\n\n const profile = (b[2]! >> 6) & 0x03;\n const audioObjectType = profile + 1;\n const asc =\n (audioObjectType << 11) | (samplingIndex << 7) | (channelConfig << 3);\n const configHex = Buffer.from([(asc >> 8) & 0xff, asc & 0xff]).toString(\n \"hex\",\n );\n return { sampleRate, channels, configHex };\n }\n\n // -----------------------------------------------------------------------\n // Native stream management\n // -----------------------------------------------------------------------\n\n private async startNativeStream(): Promise<void> {\n if (this.nativeStreamActive) return;\n\n // Ensure the API's control socket is connected. Battery cameras use\n // idle_disconnect: the control socket closes between stream sessions but\n // the API object stays valid (isClosed = false). ensureConnected()\n // reconnects transparently on-demand.\n if (!this.api.isReady) {\n if (this.api.isClosed) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] API has been explicitly closed — stream cannot start`,\n );\n return;\n }\n try {\n this.logger.info?.(\n `[Go2rtcTcpServer] API not ready (idle disconnect?), calling ensureConnected`,\n );\n await this.api.ensureConnected();\n } catch (e) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] ensureConnected failed, aborting stream start: ${e}`,\n );\n return;\n }\n }\n\n this.nativeStreamActive = true;\n\n // Acquire dedicated session if deviceId is set\n let dedicatedClient: import(\"../../client/BaichuanClient\").BaichuanClient | undefined;\n if (this.deviceId) {\n try {\n // Session key MUST start with \"live:\" so the library's resolveSocketTag()\n // assigns a per-profile socket tag (e.g. \"streaming:ch0:main\").\n // Without the \"live:\" prefix, all streams fall back to the shared \"general\"\n // socket, causing streamType mismatches between main/sub/ext.\n const session = await this.api.createDedicatedSession(\n `live:${this.deviceId}:ch${this.channel}:${this.profile}`,\n );\n dedicatedClient = session.client;\n this.dedicatedSessionRelease = session.release;\n } catch (e) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] failed to acquire dedicated session, using shared socket: ${e}`,\n );\n }\n }\n\n this.logger.info?.(\n `[Go2rtcTcpServer] native stream starting channel=${this.channel} profile=${this.profile} dedicated=${!!dedicatedClient}`,\n );\n\n // Per-session flag: true once at least one frame is received.\n // Used in onEnd to distinguish \"camera sleeping (never sent frames)\" from\n // \"camera dropped mid-stream\" — only the latter should trigger a restart.\n let hadFrames = false;\n\n this.nativeFanout = new NativeStreamFanout({\n maxQueueItems: 200,\n createSource: () =>\n createNativeStream(this.api, this.channel, this.profile, {\n variant: this.variant,\n ...(dedicatedClient ? { client: dedicatedClient } : {}),\n }),\n onFrame: (frame) => {\n // Update stream health tracking\n hadFrames = true;\n this.lastFrameAt = Date.now();\n this.totalFramesReceived++;\n\n // Detect video type from first video frame\n if (!frame.audio && (frame.videoType === \"H264\" || frame.videoType === \"H265\")) {\n this.detectedVideoType = frame.videoType;\n }\n\n // Convert and add to prebuffer\n // Video: convert to Annex-B; Audio: store raw ADTS (muxing happens per-client)\n let prebufData: Buffer;\n let isKeyframe: boolean;\n\n if (frame.audio) {\n if (frame.data.length === 0) return;\n // Populate audioInfo on the first valid ADTS frame so getAudioInfo()\n // (used by the stream-diagnostic feature) can return metadata.\n if (!this.audioInfo) {\n const parsed = Go2rtcTcpServer.parseAdtsSamplingInfo(frame.data);\n if (parsed) {\n this.audioInfo = { codec: \"aac-adts\", ...parsed };\n }\n }\n prebufData = frame.data;\n isKeyframe = false;\n } else {\n const annexB = this.convertVideoFrame(frame);\n if (!annexB || annexB.length === 0) return;\n prebufData = annexB;\n isKeyframe = this.isAnnexBKeyframe(annexB, frame.videoType);\n }\n\n const pts = frame.microseconds ?? Date.now() * 1000;\n this.prebuffer.push({\n data: Buffer.from(prebufData),\n time: Date.now(),\n isKeyframe,\n audio: frame.audio,\n pts,\n });\n\n // Trim prebuffer to window\n const cutoff = Date.now() - this.prebufferMaxMs;\n let trimIdx = 0;\n while (trimIdx < this.prebuffer.length && this.prebuffer[trimIdx]!.time < cutoff) {\n trimIdx++;\n }\n if (trimIdx > 0) this.prebuffer.splice(0, trimIdx);\n },\n onError: (error) => {\n this.logger.warn?.(`[Go2rtcTcpServer] native stream error: ${error}`);\n },\n onEnd: () => {\n if (!this.nativeStreamActive) return;\n this.nativeStreamActive = false;\n this.nativeFanout = null;\n this.stopStreamHealthMonitor();\n\n const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;\n const diagnosis = silenceMs > this.streamTimeoutMs\n ? \"camera stopped sending frames\"\n : silenceMs >= 0\n ? \"stream source closed\"\n : \"no frames were ever received\";\n this.logger.warn?.(\n `[Go2rtcTcpServer] native stream ended diagnosis=\"${diagnosis}\" ` +\n `lastFrame=${silenceMs >= 0 ? `${(silenceMs / 1000).toFixed(1)}s ago` : \"never\"} ` +\n `totalRx=${this.totalFramesReceived} clients=${this.connectedClients.size}`,\n );\n\n // Release dedicated session\n if (this.dedicatedSessionRelease) {\n this.dedicatedSessionRelease().catch(() => {});\n this.dedicatedSessionRelease = undefined;\n }\n\n // Auto-restart policy:\n // - AC-powered cameras (prestartStream=true): always restart to keep\n // the stream warm. They are expected to come back online quickly.\n // - Battery cameras (prestartStream=false): NEVER auto-restart. The\n // native stream ending for a battery camera means the camera went\n // back to sleep. Restarting would immediately wake the camera\n // again, creating an awake→sleep→awake loop for as long as a\n // downstream consumer (go2rtc RTSP client, HA snapshot poller,\n // etc.) holds the TCP connection open. Instead we drop all TCP\n // clients so go2rtc sees EOF and propagates the disconnect to its\n // consumers — they must explicitly reconnect to re-wake the camera.\n if (!this.prestartStream) {\n this.logger.info?.(\n `[Go2rtcTcpServer] battery native stream ended hadFrames=${hadFrames} ` +\n `channel=${this.channel} profile=${this.profile} — dropping ${this.connectedClients.size} client(s) to prevent wake loop`,\n );\n for (const [, sock] of this.clientSockets) {\n sock.destroy();\n }\n } else if (this.active) {\n // If the device explicitly rejected this profile (response_code 400)\n // there is no point in restarting — the camera will reject every\n // subsequent attempt, spamming the logs. Drop clients and stay\n // stopped until the consumer explicitly removes/recreates this\n // server (or the API instance is recycled).\n if (\n typeof this.api.isStreamProfileRejected === \"function\" &&\n this.api.isStreamProfileRejected(this.channel, this.profile)\n ) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] profile rejected by device channel=${this.channel} profile=${this.profile} — not restarting`,\n );\n for (const [, sock] of this.clientSockets) {\n sock.destroy();\n }\n return;\n }\n this.logger.info?.(\n `[Go2rtcTcpServer] restarting native stream (clients=${this.connectedClients.size}, prestart=${this.prestartStream})`,\n );\n this.startNativeStream();\n }\n },\n });\n\n this.nativeFanout.start();\n this.startStreamHealthMonitor();\n }\n\n private async stopNativeStream(): Promise<void> {\n this.nativeStreamActive = false;\n this.stopStreamHealthMonitor();\n const fanout = this.nativeFanout;\n this.nativeFanout = null;\n if (fanout) {\n await fanout.stop();\n }\n this.prebuffer = [];\n\n if (this.dedicatedSessionRelease) {\n await this.dedicatedSessionRelease().catch(() => {});\n this.dedicatedSessionRelease = undefined;\n }\n }\n\n // -----------------------------------------------------------------------\n // Stream health monitoring\n // -----------------------------------------------------------------------\n\n private startStreamHealthMonitor(): void {\n this.stopStreamHealthMonitor();\n if (this.streamTimeoutMs <= 0) return;\n\n this.lastFrameAt = Date.now();\n this.streamHealthTimer = setInterval(() => {\n if (!this.nativeStreamActive || !this.active) {\n this.stopStreamHealthMonitor();\n return;\n }\n\n const silenceMs = Date.now() - this.lastFrameAt;\n if (silenceMs > this.streamTimeoutMs) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] stream inactivity timeout: no frames for ${(silenceMs / 1000).toFixed(1)}s (threshold=${this.streamTimeoutMs}ms), ` +\n `totalReceived=${this.totalFramesReceived} clients=${this.connectedClients.size} — forcing stream restart`,\n );\n this.stopStreamHealthMonitor();\n\n // Force-stop the native stream; the onEnd callback will auto-restart\n // if clients are connected or prestartStream is enabled.\n const fanout = this.nativeFanout;\n if (fanout) {\n this.nativeStreamActive = false;\n this.nativeFanout = null;\n fanout.stop().catch(() => {});\n }\n }\n }, Math.min(this.streamTimeoutMs / 2, 5_000));\n }\n\n private stopStreamHealthMonitor(): void {\n if (this.streamHealthTimer) {\n clearInterval(this.streamHealthTimer);\n this.streamHealthTimer = undefined;\n }\n }\n\n // -----------------------------------------------------------------------\n // Client lifecycle\n // -----------------------------------------------------------------------\n\n private removeClient(clientId: string, reason?: string): void {\n if (!this.connectedClients.has(clientId)) return;\n this.connectedClients.delete(clientId);\n this.clientSockets.delete(clientId);\n\n const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;\n const silenceInfo = silenceMs >= 0 ? ` lastFrame=${(silenceMs / 1000).toFixed(1)}s ago` : \"\";\n this.logger.info?.(\n `[Go2rtcTcpServer] client disconnected id=${clientId} reason=${reason ?? \"unknown\"}` +\n ` remaining=${this.connectedClients.size} totalRx=${this.totalFramesReceived} totalTx=${this.totalVideoFramesWritten}${silenceInfo}`,\n );\n this.emit(\"clientDisconnected\", clientId);\n\n if (this.connectedClients.size === 0 && !this.prestartStream) {\n // Only schedule stop if prestart is off. When prestart is on, the native\n // stream stays alive permanently so go2rtc can reconnect at any time.\n this.scheduleStop();\n }\n }\n\n private scheduleStop(): void {\n if (this.stopGraceTimer) return;\n this.logger.info?.(\n `[Go2rtcTcpServer] no clients, scheduling stream stop in ${this.gracePeriodMs}ms`,\n );\n this.stopGraceTimer = setTimeout(async () => {\n this.stopGraceTimer = undefined;\n if (this.connectedClients.size === 0 && this.nativeStreamActive) {\n this.logger.info?.(\"[Go2rtcTcpServer] grace period expired, stopping native stream\");\n await this.stopNativeStream();\n }\n }, this.gracePeriodMs);\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 // createNativeStream automatically acquires a dedicated socket from the pool.\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\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 /** Limit the UDP port range used by ICE (useful for Docker port publishing) */\n icePortRange?: [number, number];\n /** Extra host addresses to advertise as candidates (e.g. host LAN IP) */\n iceAdditionalHostAddresses?: string[];\n /** Force relay-only (TURN) if needed */\n iceTransportPolicy?: \"all\" | \"relay\";\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 icePortRange: this.options.icePortRange,\n iceAdditionalHostAddresses: this.options.iceAdditionalHostAddresses,\n iceTransportPolicy: this.options.iceTransportPolicy,\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 // createNativeStream automatically acquires a dedicated socket from the pool.\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 type { BaichuanVideoStream } from \"./BaichuanVideoStream\";\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 /**\n * External video stream from a shared pool (e.g. createRfc4571TcpServer).\n * When provided, the HLS server subscribes to this stream's events instead\n * of creating its own via createNativeStream. This allows multiple outputs\n * (MJPEG, HLS, WebRTC) to share a single camera streaming session.\n */\n externalVideoStream?: BaichuanVideoStream;\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 externalVideoStream: BaichuanVideoStream | undefined;\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.externalVideoStream = options.externalVideoStream;\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 // Use external video stream if provided (shared pool), otherwise create our own.\n if (this.externalVideoStream) {\n this.nativeStream = this.wrapVideoStreamAsGenerator(this.externalVideoStream);\n } else {\n // createNativeStream automatically acquires a dedicated socket from the pool.\n this.nativeStream = createNativeStream(\n this.api,\n this.channel,\n this.profile,\n this.variant ? { variant: this.variant } : undefined,\n );\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 /**\n * Wrap a BaichuanVideoStream's videoAccessUnit events into an async generator\n * compatible with createNativeStream's output format.\n */\n private async *wrapVideoStreamAsGenerator(\n videoStream: BaichuanVideoStream,\n ): AsyncGenerator<{\n audio: boolean;\n data: Buffer;\n videoType?: \"H264\" | \"H265\";\n isKeyframe?: boolean;\n microseconds: number | null;\n }, void, unknown> {\n type Frame = {\n data: Buffer;\n isKeyframe: boolean;\n videoType: \"H264\" | \"H265\";\n microseconds: number;\n };\n\n const queue: Frame[] = [];\n let resolve: (() => void) | null = null;\n let done = false;\n\n const onFrame = (au: Frame) => {\n queue.push(au);\n resolve?.();\n resolve = null;\n };\n\n const onClose = () => {\n done = true;\n resolve?.();\n resolve = null;\n };\n\n const onError = () => {\n done = true;\n resolve?.();\n resolve = null;\n };\n\n videoStream.on(\"videoAccessUnit\", onFrame);\n videoStream.on(\"close\", onClose);\n videoStream.on(\"error\", onError);\n\n try {\n while (!done) {\n if (queue.length === 0) {\n await new Promise<void>((r) => { resolve = r; });\n }\n while (queue.length > 0) {\n const frame = queue.shift()!;\n yield {\n audio: false,\n data: frame.data,\n videoType: frame.videoType,\n isKeyframe: frame.isKeyframe,\n microseconds: frame.microseconds,\n };\n }\n }\n } finally {\n videoStream.removeListener(\"videoAccessUnit\", onFrame);\n videoStream.removeListener(\"close\", onClose);\n videoStream.removeListener(\"error\", onError);\n }\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAM,cAAc,OAClB,GACA,IACA,UACe;AACf,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK;AAAA,MACxB;AAAA,MACA,IAAI,QAAW,CAAC,GAAG,WAAW;AAC5B,YAAI;AAAA,UACF,MAAM,OAAO,IAAI,MAAM,GAAG,KAAK,oBAAoB,EAAE,IAAI,CAAC;AAAA,UAC1D;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,UAAE;AACA,QAAI,EAAG,cAAa,CAAC;AAAA,EACvB;AACF;AA0EO,IAAM,oBAAN,MAAwB;AAAA,EAO7B,YACmB,KACjB,SACA;AAFiB;AAGjB,SAAK,SAAS,SAAS;AACvB,SAAK,eAAe,SAAS,gBAAgB,IAAI,KAAK;AAGtD,UAAM,oBAAoB,SAAS,qBAAqB;AACxD,SAAK,eAAe,YAAY,MAAM;AACpC,WAAK,KAAK,uBAAuB;AAAA,IACnC,GAAG,iBAAiB;AAAA,EACtB;AAAA,EAlBQ,WAAW,oBAAI,IAA6B;AAAA,EACnC;AAAA,EACA;AAAA,EACT;AAAA,EACA,gBAAgB,oBAAI,IAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBvD,MAAM,cAAc,QAgBS;AAC3B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AAEJ,QAAI;AAEF,UAAI,QAAQ,KAAK,SAAS,IAAI,UAAU;AAExC,YAAM,aAAa,YAAY,mBAAmB,YAAY;AAC9D,YAAM,YAAY,QAAQ,SAAS,KAAK;AAMxC,UAAI,CAAC,SAAS,WAAW;AACvB,aAAK,QAAQ;AAAA,UACX,yFAAyF,UAAU,IAAI,OAAO;AAAA,QAChH;AACA,eAAO;AAAA,UACL,YAAY;AAAA,UACZ,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,iBAAiB;AAAA,YACjB,QAAQ;AAAA,YACR,eAAe;AAAA,UACjB;AAAA,UACA,MAAM;AAAA,QACR;AAAA,MACF;AAEA,UAAI,CAAC,OAAO;AAEV,YAAI,CAAC,YAAY;AACf,iBAAO;AAAA,YACL,YAAY;AAAA,YACZ,SAAS,EAAE,gBAAgB,aAAa;AAAA,YACxC,MAAM;AAAA,UACR;AAAA,QACF;AAKA,cAAM,UAAU,sBAAsB;AACtC,cAAM,KAAK,iBAAiB,SAAS,YAAY;AAE/C,kBAAQ,KAAK,SAAS,IAAI,UAAU;AACpC,cAAI,MAAO;AAEX,cAAI,oBAAoB;AACtB,kBAAM,KAAK;AAAA,cACT;AAAA,cACA;AAAA,YACF;AAAA,UACF;AAEA,eAAK,QAAQ;AAAA,YACX,6CAA6C,UAAU;AAAA,UACzD;AAEA,eAAK,QAAQ;AAAA,YACX,wCAAwC,UAAU;AAAA,UACpD;AACA,gBAAM,gBAAgB,MAAM,cAAc;AAE1C,eAAK,QAAQ;AAAA,YACX,iEAAiE,UAAU;AAAA,UAC7E;AACA,gBAAM,UAAU,MAAM;AAAA,YACpB,KAAK,IAAI,gCAAgC;AAAA,cACvC,SAAS,cAAc;AAAA,cACvB,UAAU,cAAc;AAAA,cACxB,GAAI,cAAc,UAAU,UAAa;AAAA,gBACvC,OAAO,cAAc;AAAA,cACvB;AAAA,cACA,GAAI,KAAK,UAAU,EAAE,QAAQ,KAAK,OAAO;AAAA,cACzC,GAAI,cAAc,YAAY;AAAA,gBAC5B,UAAU,cAAc;AAAA,cAC1B;AAAA,cACA,qBAAqB,cAAc,uBAAuB;AAAA,cAC1D,oBAAoB,cAAc,sBAAsB;AAAA,YAC1D,CAAC;AAAA,YACD;AAAA,YACA;AAAA,UACF;AAIA,cAAI;AACF,kBAAM;AAAA,cACJ,QAAQ,aAAa;AAAA,cACrB;AAAA,cACA;AAAA,YACF;AAAA,UACF,SAAS,GAAG;AACV,iBAAK,QAAQ;AAAA,cACX,iEAAiE,UAAU,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,YAC5H;AAAA,UACF;AAEA,kBAAQ;AAAA,YACN;AAAA,YACA,WAAW,KAAK,IAAI;AAAA,YACpB,cAAc,KAAK,IAAI;AAAA,UACzB;AACA,eAAK,SAAS,IAAI,YAAY,KAAK;AAEnC,eAAK,QAAQ;AAAA,YACX,sCAAsC,UAAU;AAAA,UAClD;AAAA,QACF,CAAC;AAGD,gBAAQ,KAAK,SAAS,IAAI,UAAU;AACpC,YAAI,CAAC,OAAO;AACV,iBAAO;AAAA,YACL,YAAY;AAAA,YACZ,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,iBAAiB;AAAA,cACjB,QAAQ;AAAA,YACV;AAAA,YACA,MAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAGA,YAAM,eAAe,KAAK,IAAI;AAG9B,UAAI,YAAY;AACd,eAAO,KAAK,cAAc,MAAM,SAAS,YAAY,UAAU;AAAA,MACjE;AAGA,UAAI,WAAW;AACb,eAAO,KAAK,aAAa,MAAM,SAAS,SAAS,UAAU;AAAA,MAC7D;AAGA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS,EAAE,gBAAgB,aAAa;AAAA,QACxC,MAAM;AAAA,MACR;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,WAAK,QAAQ;AAAA,QACX,+CAA+C,OAAO;AAAA,MACxD;AAEA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS,EAAE,gBAAgB,aAAa;AAAA,QACxC,MAAM,cAAc,OAAO;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,iBACZ,SACA,IACe;AACf,UAAM,OAAO,KAAK,cAAc,IAAI,OAAO,KAAK,QAAQ,QAAQ;AAChE,QAAI;AACJ,UAAM,UAAU,IAAI,QAAc,CAAC,YAAY;AAC7C,gBAAU;AAAA,IACZ,CAAC;AACD,UAAM,UAAU,KAAK;AAAA,MACnB,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AACA,SAAK,cAAc,IAAI,SAAS,OAAO;AAEvC,UAAM,KAAK,MAAM,MAAM;AAAA,IAAC,CAAC;AACzB,QAAI;AACF,YAAM,GAAG;AAAA,IACX,UAAE;AACA,cAAQ;AACR,UAAI,KAAK,cAAc,IAAI,OAAO,MAAM,SAAS;AAC/C,aAAK,cAAc,OAAO,OAAO;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,YAA6B;AACtC,WAAO,KAAK,SAAS,IAAI,UAAU;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,YAAmC;AACnD,UAAM,QAAQ,KAAK,SAAS,IAAI,UAAU;AAC1C,QAAI,OAAO;AACT,WAAK,QAAQ;AAAA,QACX,yCAAyC,UAAU;AAAA,MACrD;AACA,WAAK,SAAS,OAAO,UAAU;AAC/B,YAAM,MAAM,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,SAAK,QAAQ,QAAQ,2CAA2C;AAEhE,QAAI,KAAK,cAAc;AACrB,oBAAc,KAAK,YAAY;AAC/B,WAAK,eAAe;AAAA,IACtB;AAEA,UAAM,eAAe,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EAAE;AAAA,MAAI,CAAC,UAC3D,MAAM,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACrC;AACA,SAAK,SAAS,MAAM;AACpB,UAAM,QAAQ,IAAI,YAAY;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,eAAuB;AACzB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKQ,cACN,SACA,YACA,YACiB;AACjB,QAAI,WAAW,QAAQ,YAAY;AAMnC,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,YAAY,kBAAkB;AAClD,YAAM,WAAW,IAAI;AAGrB,YAAM,aAAa,IAAI,gBAAgB,IAAI,YAAY;AACvD,iBAAW,OAAO,KAAK;AAGvB,iBAAW,SAAS,QAAQ,yBAAyB,CAAC,UAAU;AAC9D,cAAM,SAAS,IAAI,gBAAgB,UAAU;AAC7C,eAAO,IAAI,OAAO,KAAK;AACvB,eAAO,GAAG,QAAQ,IAAI,OAAO,SAAS,CAAC;AAAA,MACzC,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAEA,SAAK,QAAQ;AAAA,MACX,yCAAyC,UAAU,YAAY,SAAS,MAAM;AAAA,IAChF;AAEA,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,QAAQ;AAAA,MACV;AAAA,MACA,MAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,aACN,SACA,aACA,YACiB;AACjB,UAAM,UAAU,QAAQ,WAAW,WAAW;AAE9C,QAAI,CAAC,SAAS;AACZ,WAAK,QAAQ;AAAA,QACX,0CAA0C,WAAW;AAAA,MACvD;AACA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,QAAQ;AAAA,UACR,eAAe;AAAA,QACjB;AAAA,QACA,MAAM;AAAA,MACR;AAAA,IACF;AAEA,SAAK,QAAQ;AAAA,MACX,wCAAwC,WAAW,QAAQ,UAAU,UAAU,QAAQ,MAAM;AAAA,IAC/F;AAEA,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,QAAQ;AAAA,QACR,kBAAkB,OAAO,QAAQ,MAAM;AAAA,MACzC;AAAA,MACA,MAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,yBAAwC;AACpD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,cAAwB,CAAC;AAE/B,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,UAAU;AACxC,UAAI,MAAM,MAAM,eAAe,KAAK,cAAc;AAChD,oBAAY,KAAK,GAAG;AAAA,MACtB;AAAA,IACF;AAEA,QAAI,CAAC,YAAY,OAAQ;AAEzB,UAAM,QAAQ;AAAA,MACZ,YAAY,IAAI,OAAO,QAAQ;AAC7B,cAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,YAAI,CAAC,MAAO;AAEZ,aAAK,QAAQ;AAAA,UACX,qDAAqD,GAAG;AAAA,QAC1D;AACA,aAAK,SAAS,OAAO,GAAG;AAExB,YAAI;AACF,gBAAM,MAAM,QAAQ,KAAK;AAAA,QAC3B,QAAQ;AAAA,QAER;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,4BACZ,QACA,WACe;AACf,UAAM,SAAmB,CAAC;AAC1B,eAAW,OAAO,KAAK,SAAS,KAAK,GAAG;AACtC,UAAI,QAAQ,aAAa,IAAI,WAAW,MAAM,EAAG,QAAO,KAAK,GAAG;AAAA,IAClE;AAEA,QAAI,CAAC,OAAO,OAAQ;AAEpB,SAAK,QAAQ;AAAA,MACX,wCAAwC,OAAO,MAAM,0BAA0B,MAAM;AAAA,IACvF;AAEA,UAAM,QAAQ;AAAA,MACZ,OAAO,IAAI,OAAO,QAAQ;AACxB,cAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,YAAI,CAAC,MAAO;AACZ,aAAK,SAAS,OAAO,GAAG;AACxB,cAAM,MAAM,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC3C,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAQO,SAAS,gBAAgB,WAI9B;AACA,QAAM,MAAM,aAAa,IAAI,YAAY;AACzC,QAAM,QAAQ,mBAAmB,KAAK,EAAE;AACxC,QAAM,oBAAoB,GAAG,SAAS,cAAc;AAEpD,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,UAAU;AAAA,EACZ;AACF;AAQO,SAAS,oBAAoB,aAA6B;AAC/D,SAAO,GAAG,WAAW,GAAG,YAAY,SAAS,GAAG,IAAI,MAAM,GAAG;AAC/D;;;ACriBO,IAAM,sBAAN,MAA0B;AAAA,EACd;AAAA,EAGA,oBAAoB,oBAAI,IAA8B;AAAA,EAC/D,YAAmC;AAAA,EACnC,YAAY;AAAA,EACZ,qBAA2C;AAAA,EAEnD,YAAY,UAAsC,CAAC,GAAG;AACpD,SAAK,UAAU;AAAA,MACb,GAAG;AAAA,MACH,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,WAAW,QAAQ,aAAa;AAAA,IAClC;AAEA,QAAI,KAAK,QAAQ,WAAW;AAC1B,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,WAAW;AAClB,WAAK,QAAQ,QAAQ,OAAO,2CAA2C;AACvE;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,SAAK,QAAQ,QAAQ;AAAA,MACnB,4DAA4D,KAAK,QAAQ,cAAc;AAAA,IACzF;AAGA,SAAK,YAAY;AAGjB,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,QAAQ,QAAQ,OAAO,uCAAuC;AACnE;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,QAAI,KAAK,WAAW;AAClB,mBAAa,KAAK,SAAS;AAC3B,WAAK,YAAY;AAAA,IACnB;AAEA,SAAK,QAAQ,QAAQ,MAAM,mCAAmC;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,uBAA2C;AACzC,WAAO,MAAM,KAAK,KAAK,kBAAkB,OAAO,CAAC,EAAE;AAAA,MAAK,CAAC,GAAG,MAC1D,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAyB;AACvB,WAAO,KAAK,kBAAkB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAyB;AAC7B,QAAI,KAAK,oBAAoB;AAC3B,WAAK,QAAQ,QAAQ;AAAA,QACnB;AAAA,MACF;AACA,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,KAAK,YAAY;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,MAAuB;AAClC,UAAM,UAAU,KAAK,kBAAkB,OAAO,IAAI;AAClD,QAAI,SAAS;AACX,WAAK,QAAQ,QAAQ,MAAM,mCAAmC,IAAI,EAAE;AAAA,IACtE;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,UAAM,QAAQ,KAAK,kBAAkB;AACrC,SAAK,kBAAkB,MAAM;AAC7B,SAAK,QAAQ,QAAQ;AAAA,MACnB,2BAA2B,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,eAAe,YAAY;AAC/B,UAAI;AACF,aAAK,QAAQ,QAAQ,MAAM,kCAAkC;AAE7D,cAAM,aAAa,MAAM,uBAAuB,KAAK,OAAO;AAE5D,cAAM,aAAiC,CAAC;AACxC,cAAM,iBAAqC,CAAC;AAE5C,mBAAW,UAAU,YAAY;AAC/B,gBAAM,WAAW,KAAK,kBAAkB,IAAI,OAAO,IAAI;AACvD,cAAI,UAAU;AACZ,kBAAM,UAAU,KAAK,gBAAgB,UAAU,MAAM;AACrD,gBAAI,SAAS;AACX,6BAAe,KAAK,OAAO;AAAA,YAC7B;AAAA,UACF,OAAO;AACL,iBAAK,kBAAkB,IAAI,OAAO,MAAM,EAAE,GAAG,OAAO,CAAC;AACrD,uBAAW,KAAK,MAAM;AAAA,UACxB;AAAA,QACF;AAEA,aAAK,QAAQ,QAAQ;AAAA,UACnB,mCAAmC,WAAW,MAAM,SAAS,eAAe,MAAM,oBAAoB,KAAK,kBAAkB,IAAI;AAAA,QACnI;AAGA,mBAAW,UAAU,YAAY;AAC/B,eAAK,QAAQ,QAAQ;AAAA,YACnB,gCAAgC,OAAO,IAAI,MAAM,OAAO,SAAS,SAAS,MAAM,OAAO,QAAQ,EAAE,UAAU,OAAO,eAAe;AAAA,UACnI;AACA,cAAI;AACF,iBAAK,QAAQ,qBAAqB,MAAM;AAAA,UAC1C,QAAQ;AAAA,UAER;AAAA,QACF;AACA,mBAAW,UAAU,gBAAgB;AACnC,cAAI;AACF,iBAAK,QAAQ,kBAAkB,MAAM;AAAA,UACvC,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,cAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,aAAK,QAAQ,QAAQ;AAAA,UACnB,sCAAsC,GAAG;AAAA,QAC3C;AAAA,MACF;AAAA,IACF,GAAG;AAEH,SAAK,qBAAqB;AAC1B,UAAM;AACN,SAAK,qBAAqB;AAAA,EAC5B;AAAA,EAEQ,gBACN,UACA,SACyB;AACzB,QAAI,aAAa;AAEjB,QAAI,CAAC,SAAS,SAAS,QAAQ,OAAO;AACpC,eAAS,QAAQ,QAAQ;AACzB,mBAAa;AAAA,IACf;AACA,QAAI,CAAC,SAAS,OAAO,QAAQ,KAAK;AAChC,eAAS,MAAM,QAAQ;AACvB,mBAAa;AAAA,IACf;AACA,QAAI,CAAC,SAAS,QAAQ,QAAQ,MAAM;AAClC,eAAS,OAAO,QAAQ;AACxB,mBAAa;AAAA,IACf;AACA,QAAI,CAAC,SAAS,mBAAmB,QAAQ,iBAAiB;AACxD,eAAS,kBAAkB,QAAQ;AACnC,mBAAa;AAAA,IACf;AACA,QAAI,QAAQ,YAAY,CAAC,SAAS,UAAU;AAC1C,eAAS,WAAW,QAAQ;AAC5B,mBAAa;AAAA,IACf;AACA,QAAI,QAAQ,aAAa,CAAC,SAAS,WAAW;AAC5C,eAAS,YAAY,QAAQ;AAC7B,mBAAa;AAAA,IACf;AACA,QACE,QAAQ,kBAAkB,UAC1B,SAAS,kBAAkB,QAAQ,eACnC;AACA,eAAS,gBAAgB,QAAQ;AACjC,mBAAa;AAAA,IACf;AACA,QACE,QAAQ,mBAAmB,UAC3B,SAAS,mBAAmB,QAAQ,gBACpC;AACA,eAAS,iBAAiB,QAAQ;AAClC,mBAAa;AAAA,IACf;AAEA,WAAO,aAAa,WAAW;AAAA,EACjC;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,CAAC,KAAK,UAAW;AAErB,SAAK,YAAY,WAAW,MAAM;AAChC,WAAK,YAAY;AACjB,UAAI,KAAK,WAAW;AAClB,aAAK,YAAY,EAAE,QAAQ,MAAM;AAC/B,cAAI,KAAK,WAAW;AAClB,iBAAK,iBAAiB;AAAA,UACxB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,GAAG,KAAK,QAAQ,cAAc;AAAA,EAChC;AACF;;;ACw5CO,SAAS,uBACd,SACqB;AACrB,QAAM,YAAY,CAAC,QAAoC;AACrD,UAAM,MACJ,QAAQ,GAAG,KAAK,QAAQ,IAAI,YAAY,CAAC,KAAK,QAAQ,IAAI,YAAY,CAAC;AACzE,WAAO,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI;AAAA,EACvC;AAEA,SAAO;AAAA,IACL,WAAW,UAAU,YAAY,KAAK,UAAU,YAAY;AAAA,IAC5D,QAAQ,UAAU,QAAQ,KAAK,UAAU,QAAQ;AAAA,IACjD,OAAO,UAAU,OAAO,KAAK,UAAU,OAAO;AAAA,IAC9C,SAAS,UAAU,WAAW,KAAK,UAAU,WAAW;AAAA,IACxD,eACE,UAAU,kBAAkB,KAAK,UAAU,kBAAkB;AAAA,IAC/D,iBACE,UAAU,oBAAoB,KAAK,UAAU,oBAAoB;AAAA,EACrE;AACF;AAgBO,SAAS,6BACd,SACA,WACuB;AACvB,QAAM,aAAa,uBAAuB,OAAO;AAGjD,MAAI,WAAW;AACb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ,WAAW,SAAS;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,WAAW,aAAa,IAAI,YAAY;AACpD,QAAM,YAAY,WAAW,mBAAmB,IAC7C,YAAY,EACZ,QAAQ,MAAM,EAAE;AAGnB,QAAM,QAAQ,mBAAmB,KAAK,EAAE;AACxC,MAAI,OAAO;AACT,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,GAAG,SAAS,SAAS;AACvC,MAAI,WAAW;AACb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,GAAG,SAAS,SAAS,KAAK,aAAa;AACzD,MAAI,WAAW;AACb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa,GAAG,SAAS,QAAQ,KAAK,GAAG,SAAS,KAAK;AAC7D,QAAM,QAAQ,GAAG,SAAS,QAAQ,KAAK,aAAa;AACpD,MAAI,cAAc,CAAC,OAAO;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO;AACT,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR;AAAA,EACF;AACF;;;ACpyDA,OAAO,UAAU;AACjB,SAAS,aAAa;AAsBtB,SAAS,cAAc,GAAkB,KAAqB;AAC5D,MAAI,KAAK,KAAM,QAAO;AACtB,QAAM,IAAI,OAAO,SAAS,GAAG,EAAE;AAC/B,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,SAAS,eAAe,GAAkB,KAAuB;AAC/D,MAAI,KAAK,KAAM,QAAO;AACtB,QAAM,IAAI,EAAE,KAAK,EAAE,YAAY;AAC/B,MAAI,MAAM,OAAO,MAAM,UAAU,MAAM,SAAS,MAAM,IAAK,QAAO;AAClE,MAAI,MAAM,OAAO,MAAM,WAAW,MAAM,QAAQ,MAAM,IAAK,QAAO;AAClE,SAAO;AACT;AAEA,SAAS,aAAa,GAAiC;AACrD,QAAM,KAAK,KAAK,OAAO,KAAK;AAC5B,MAAI,MAAM,UAAU,MAAM,SAAS,MAAM,MAAO,QAAO;AACvD,QAAM,IAAI,MAAM,6CAA6C;AAC/D;AAEA,SAAS,mBAAmB,GAAsC;AAChE,QAAM,KAAK,KAAK,WAAW,KAAK,EAAE,YAAY;AAC9C,MAAI,MAAM,MAAM,MAAM,UAAW,QAAO;AACxC,MAAI,MAAM,YAAa,QAAO;AAC9B,MAAI,MAAM,YAAa,QAAO;AAC9B,QAAM,IAAI,MAAM,4DAA4D;AAC9E;AAEA,SAAS,eAAe,GAAwB;AAC9C,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,cAAc;AACtC,QAAM,IAAI,IAAI,KAAK,CAAC;AACpB,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC;AAC1B,UAAM,IAAI,MAAM,oCAAoC;AACtD,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAc,GAAwB;AACjE,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,WAAW,IAAI,EAAE;AACzC,QAAM,IAAI,IAAI,KAAK,CAAC;AACpB,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC;AAC1B,UAAM,IAAI,MAAM,WAAW,IAAI,wBAAwB;AACzD,SAAO;AACT;AAOO,SAAS,8BACd,MACa;AACb,QAAM,MAAM,IAAI,mBAAmB;AAAA,IACjC,GAAG,KAAK;AAAA,EACV,CAAC;AAED,QAAM,aAAa,IAAI,kBAAkB,KAAK;AAAA,IAC5C,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AAED,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,iBAAiB,KAAK,kBAAkB;AAG9C,QAAM,cAAc,oBAAI,IAA6B;AAErD,QAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,QAAI;AACF,UAAI,CAAC,IAAI,KAAK;AACZ,YAAI,aAAa;AACjB,YAAI,IAAI,aAAa;AACrB;AAAA,MACF;AAEA,YAAM,IAAI,IAAI,IAAI,IAAI,KAAK,UAAU,UAAU,IAAI,KAAK,UAAU,EAAE;AAEpE,UAAI,IAAI,WAAW,OAAO;AACxB,YAAI,aAAa;AACjB,YAAI,UAAU,SAAS,KAAK;AAC5B,YAAI,IAAI,oBAAoB;AAC5B;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,WAAW;AAC5B,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,UAAU,aAAa,EAAE,aAAa,IAAI,SAAS,CAAC;AAC1D,cAAM,UAAU,mBAAmB,EAAE,aAAa,IAAI,SAAS,CAAC;AAChE,YAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG;AAC5C,cAAI,aAAa;AACjB,cAAI,IAAI,iBAAiB;AACzB;AAAA,QACF;AAEA,cAAM,MAAM,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO;AAC5C,cAAM,SAAS,YAAY,IAAI,GAAG;AAClC,YAAI,QAAQ;AACV,cAAI,UAAU,gBAAgB,kBAAkB;AAChD,cAAI,IAAI,KAAK,UAAU,EAAE,SAAS,OAAO,IAAI,CAAC,CAAC;AAC/C;AAAA,QACF;AAEA,cAAM,OAAO,MAAM,IAAI,iBAAiB,SAAS,SAAS;AAAA,UACxD,YAAY;AAAA,UACZ,YAAY;AAAA,UACZ,MAAM,WAAW,OAAO,IAAI,OAAO,GAAG,YAAY,YAAY,KAAK,IAAI,OAAO,EAAE;AAAA,UAChF,GAAI,YAAY,YAAY,CAAC,IAAI,EAAE,QAAQ;AAAA,QAC7C,CAAC;AAED,cAAM,UAAU,KAAK,WAAW;AAChC,oBAAY,IAAI,KAAK,EAAE,KAAK,QAAQ,CAAC;AAErC,YAAI,UAAU,gBAAgB,kBAAkB;AAChD,YAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,CAAC,CAAC;AACnC;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,QAAQ;AACzB,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,IAAI,KAAK;AAC7D,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,QAAQ,KAAK;AACjE,cAAM,QAAQ,eAAe,EAAE,aAAa,IAAI,OAAO,GAAG,KAAK;AAC/D,cAAM,YAAY,eAAe,EAAE,aAAa,IAAI,WAAW,GAAG,IAAI;AACtE,cAAM,qBAAqB;AAAA,UACzB,EAAE,aAAa,IAAI,oBAAoB;AAAA,UACvC;AAAA,QACF;AACA,cAAM,WAAW,EAAE,aAAa,IAAI,KAAK,KAAK,iBAAiB,KAAK;AAEpE,YAAI,CAAC,UAAU;AACb,cAAI,aAAa;AACjB,cAAI,IAAI,kBAAkB;AAC1B;AAAA,QACF;AAEA,cAAM,aAAa,OAAO,QAAQ,MAAM,OAAO,IAAI,QAAQ;AAC3D,cAAM,qBAAqB,OAAO,QAAQ,MAAM,OAAO;AAGvD,cAAM,aAAa,UAAU,UAAU,IAAI,KAAK,UAAU,GAAG,EAAE,QAAQ,GAAG,EAAE,MAAM;AAElF,cAAM,SAAS,MAAM,WAAW,cAAc;AAAA,UAC5C;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe,OAAO;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,qBAAqB;AAAA,YACrB;AAAA,UACF;AAAA,QACF,CAAC;AAED,YAAI,aAAa,OAAO;AACxB,mBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AACnD,cAAI,UAAU,GAAG,CAAC;AAAA,QACpB;AACA,YAAI,IAAI,OAAO,IAAI;AACnB;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,aAAa;AAC9B,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,OAAO,EAAE,aAAa,IAAI,KAAK,KAAK,IAAI,KAAK;AACnD,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,IAAI,KAAK;AAC7D,cAAM,YAAY;AAAA,UAChB,EAAE,aAAa,IAAI,WAAW;AAAA,UAC9B;AAAA,QACF;AAEA,YAAI,CAAC,KAAK;AACR,cAAI,aAAa;AACjB,cAAI,IAAI,aAAa;AACrB;AAAA,QACF;AACA,YAAI,CAAC,UAAU;AACb,cAAI,aAAa;AACjB,cAAI,IAAI,kBAAkB;AAC1B;AAAA,QACF;AAEA,cAAM,MAAM,MAAM,IAAI,kBAAkB;AAAA,UACtC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,cAAM,UACJ,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,GAAG,EAAE,KAAK;AAChD,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,0BAA0B;AACxD,YAAI;AAAA,UACF;AAAA,UACA,yBAAyB,OAAO;AAAA,QAClC;AACA,YAAI,UAAU,kBAAkB,OAAO,IAAI,MAAM,CAAC;AAClD,YAAI,IAAI,GAAG;AACX;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,eAAe;AAChC,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,cACJ,EAAE,aAAa,IAAI,YAAY,KAAK,aACpC,KAAK;AACP,cAAM,QAAQ,oBAAoB,SAAS,EAAE,aAAa,IAAI,OAAO,CAAC;AACtE,cAAM,MAAM,oBAAoB,OAAO,EAAE,aAAa,IAAI,KAAK,CAAC;AAChE,cAAM,cAAc,EAAE,aAAa,IAAI,YAAY,KAAK,IAAI,KAAK;AACjE,cAAM,QAAQ,cAAc,EAAE,aAAa,IAAI,OAAO,GAAG,CAAC;AAC1D,cAAM,YAAY;AAAA,UAChB,EAAE,aAAa,IAAI,WAAW;AAAA,UAC9B;AAAA,QACF;AAEA,YAAI,eAAe,gBAAgB,eAAe,aAAa;AAC7D,cAAI,aAAa;AACjB,cAAI,IAAI,sDAAsD;AAC9D;AAAA,QACF;AAEA,YAAI,aAAa,MAAM,IAAI,cAAc;AAAA,UACvC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,UACnC;AAAA,QACF,CAAC;AAED,YAAI,QAAQ,EAAG,cAAa,WAAW,MAAM,CAAC,KAAK;AAEnD,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,kBAAkB;AAChD,YAAI;AAAA,UACF,KAAK,UAAU;AAAA,YACb;AAAA,YACA;AAAA,YACA,OAAO,MAAM,YAAY;AAAA,YACzB,KAAK,IAAI,YAAY;AAAA,YACrB;AAAA,UACF,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,iBAAiB,EAAE,aAAa,iBAAiB;AAClE,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,IAAI,KAAK;AAC7D,cAAM,cACJ,EAAE,aAAa,IAAI,YAAY,KAAK,cACpC,KAAK;AACP,cAAM,iBACJ,EAAE,aAAa,IAAI,eAAe,KAAK,OACvC,KAAK;AAEP,YAAI,CAAC,UAAU;AACb,cAAI,aAAa;AACjB,cAAI,IAAI,kBAAkB;AAC1B;AAAA,QACF;AACA,YAAI,eAAe,gBAAgB,eAAe,aAAa;AAC7D,cAAI,aAAa;AACjB,cAAI,IAAI,sDAAsD;AAC9D;AAAA,QACF;AACA,YAAI,kBAAkB,SAAS,kBAAkB,OAAO;AACtD,cAAI,aAAa;AACjB,cAAI,IAAI,4CAA4C;AACpD;AAAA,QACF;AAEA,cAAM,UAAU,MAAM,IAAI,cAAc;AAAA,UACtC;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe;AAAA,QACjB,CAAC;AAED,cAAM,UACJ,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,GAAG,EAAE,KAAK;AAEhD,YAAI,EAAE,aAAa,eAAe;AAEhC,cAAI,UAAU,KAAK;AAAA,YACjB,gBAAgB;AAAA,YAChB,iBAAiB;AAAA,YACjB,YAAY;AAAA,UACd,CAAC;AAED,gBAAMA,MAAK,MAAM,UAAU;AAAA,YACzB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAED,UAAAA,IAAG,OAAO,KAAK,GAAG;AAElB,gBAAMC,WAAU,MAAM;AACpB,YAAAD,IAAG,KAAK,SAAS;AAAA,UACnB;AACA,cAAI,GAAG,SAASC,QAAO;AACvB,cAAI,GAAG,SAASA,QAAO;AACvB,UAAAD,IAAG,GAAG,SAAS,MAAM;AACnB,gBAAI,IAAI;AAAA,UACV,CAAC;AACD;AAAA,QACF;AAGA,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,WAAW;AACzC,YAAI;AAAA,UACF;AAAA,UACA,yBAAyB,OAAO;AAAA,QAClC;AACA,YAAI,UAAU,iBAAiB,UAAU;AACzC,YAAI,UAAU,cAAc,OAAO;AAEnC,cAAM,KAAK,MAAM,UAAU;AAAA,UACzB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,WAAG,OAAO,KAAK,GAAG;AAElB,cAAM,UAAU,MAAM;AACpB,aAAG,KAAK,SAAS;AAAA,QACnB;AACA,YAAI,GAAG,SAAS,OAAO;AACvB,YAAI,GAAG,SAAS,OAAO;AACvB,WAAG,GAAG,SAAS,MAAM;AACnB,cAAI,IAAI;AAAA,QACV,CAAC;AACD;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,qBAAqB;AACtC,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,OAAO,eAAe,EAAE,aAAa,IAAI,MAAM,CAAC;AACtD,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,OAAO,KAAK;AAChE,cAAM,YAAY;AAAA,UAChB,EAAE,aAAa,IAAI,WAAW;AAAA,UAC9B;AAAA,QACF;AACA,cAAM,cAAc,cAAc,EAAE,aAAa,IAAI,aAAa,GAAG,CAAC;AAEtE,YAAI,aAAa,UAAU,aAAa,OAAO;AAC7C,cAAI,aAAa;AACjB,cAAI,IAAI,wCAAwC;AAChD;AAAA,QACF;AAEA,cAAM,EAAE,MAAM,SAAS,IAAI,MAAM,IAAI,6BAA6B;AAAA,UAChE;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,YAAY;AAC1C,YAAI,UAAU,iBAAiB,UAAU;AACzC,YAAI,UAAU,sBAAsB,SAAS,QAAQ;AACrD,YAAI,UAAU,0BAA0B,OAAO,SAAS,WAAW,CAAC;AACpE,YAAI,UAAU,kBAAkB,OAAO,KAAK,MAAM,CAAC;AACnD,YAAI,IAAI,IAAI;AACZ;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,sBAAsB;AACvC,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,IAAI,KAAK;AAE7D,YAAI,CAAC,UAAU;AACb,cAAI,aAAa;AACjB,cAAI,IAAI,kBAAkB;AAC1B;AAAA,QACF;AAEA,cAAM,EAAE,KAAK,KAAK,IAAI,MAAM,IAAI,+BAA+B;AAAA,UAC7D;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,YAAY;AAAA,QACd,CAAC;AACD,YAAI,KAAK,GAAG;AAEZ,cAAM,UAAU,MAAM;AACpB,eAAK,KAAK;AACV,cAAI;AACF,gBAAI,QAAQ;AAAA,UACd,QAAQ;AAAA,UAER;AAAA,QACF;AACA,YAAI,GAAG,SAAS,OAAO;AACvB,YAAI,GAAG,SAAS,OAAO;AACvB,YAAI,GAAG,OAAO,MAAM;AAClB,cAAI,IAAI;AAAA,QACV,CAAC;AACD,YAAI,GAAG,SAAS,MAAM;AACpB,cAAI,IAAI;AAAA,QACV,CAAC;AACD;AAAA,MACF;AAEA,UAAI,aAAa;AACjB,UAAI,IAAI,WAAW;AAAA,IACrB,SAAS,GAAG;AACV,UAAI,aAAa;AACjB,UAAI,UAAU,gBAAgB,YAAY;AAC1C,UAAI,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpD;AAAA,EACF,CAAC;AAGD,SAAO,GAAG,SAAS,MAAM;AACvB,SAAK,IAAI,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EACxC,CAAC;AAED,SAAO;AACT;;;AC3dA,OAAOE,WAAU;AACjB,SAAS,SAAAC,cAAa;AAwBf,SAAS,sBAAsB,MAA2C;AAC/E,SAAOC,MAAK,aAAa,CAAC,KAAK,QAAQ;AACrC,QAAI,CAAC,IAAI,KAAK;AACZ,UAAI,aAAa;AACjB,UAAI,IAAI,aAAa;AACrB;AAAA,IACF;AACA,UAAM,IAAI,IAAI,IAAI,IAAI,KAAK,oBAAoB,KAAK,UAAU,EAAE;AAChE,QAAI,EAAE,aAAa,WAAW;AAC5B,UAAI,aAAa;AACjB,UAAI,IAAI,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,UAAU,OAAO,EAAE,aAAa,IAAI,SAAS,KAAK,GAAG;AAC3D,UAAM,UAAW,EAAE,aAAa,IAAI,SAAS,KAAK;AAClD,QAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG;AAC5C,UAAI,aAAa;AACjB,UAAI,IAAI,iBAAiB;AACzB;AAAA,IACF;AACA,QAAI,YAAY,UAAU,YAAY,SAAS,YAAY,OAAO;AAChE,UAAI,aAAa;AACjB,UAAI,IAAI,6CAA6C;AACrD;AAAA,IACF;AAEA,UAAM,UAAU,aAAa;AAAA,MAC3B,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf;AAAA,MACA,QAAQ;AAAA,MACR,GAAI,KAAK,aAAa,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS;AAAA,IAC/D,CAAC;AAED,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,YAAY;AAAA,IACd,CAAC;AAED,UAAM,gBAAgB,KAAK,iBAAiB;AAC5C,UAAM,KAAKC,OAAM,UAAU;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,OAAG,OAAO,KAAK,GAAG;AAClB,OAAG,OAAO,GAAG,QAAQ,MAAM;AAAA,IAE3B,CAAC;AAED,UAAM,UAAU,MAAM;AACpB,SAAG,KAAK,SAAS;AAAA,IACnB;AACA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AACvB,OAAG,GAAG,SAAS,MAAM;AACnB,UAAI,IAAI;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;;;ACjGA,OAAO,YAAY;AAkCZ,SAAS,gBACd,OACA,OACQ;AACR,MAAI,MAAM;AACV,SAAO;AACP,SAAO;AACP,SAAO;AAEP,SAAO,qBAAqB,MAAM,WAAW;AAAA;AAC7C,SAAO;AACP,SAAO,YAAY,MAAM,WAAW,IAAI,MAAM,SAAS;AAAA;AAEvD,MAAI,MAAM,cAAc,UAAU,MAAM,MAAM;AAC5C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,UAAM,MAAM,MAAM,KAAK,iBACnB,oBAAoB,MAAM,KAAK,cAAc,MAC7C;AACJ,WAAO,UAAU,MAAM,WAAW,yBAAyB,GAAG,wBAAwB,MAAM,IAAI,MAAM;AAAA;AAAA,EACxG;AAEA,MAAI,MAAM,cAAc,UAAU,MAAM,MAAM;AAC5C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,WAAO,UAAU,MAAM,WAAW,cAAc,MAAM,cAAc,MAAM,cAAc,MAAM;AAAA;AAAA,EAChG;AAEA,MAAI,OAAO,UAAU,OAAO;AAC1B,WAAO,qBAAqB,MAAM,WAAW;AAAA;AAC7C,WAAO;AACP,WAAO;AACP,WAAO,YAAY,MAAM,WAAW,kBAAkB,MAAM,UAAU,IAAI,MAAM,QAAQ;AAAA;AACxF,WAAO,UAAU,MAAM,WAAW,+FAA+F,MAAM,SAAS;AAAA;AAAA,EAClJ;AAEA,MAAI,OAAO,UAAU,QAAQ;AAC3B,WAAO,qBAAqB,MAAM,WAAW;AAAA;AAC7C,WAAO;AACP,WAAO;AACP,WAAO,YAAY,MAAM,WAAW,SAAS,MAAM,UAAU,IAAI,MAAM,QAAQ;AAAA;AAAA,EACjF;AAEA,SAAO;AACT;AAEO,SAAS,kBAAkB,QAA0B;AAI1D,QAAM,OAAiB,CAAC;AACxB,QAAM,MAAM,OAAO;AAEnB,QAAM,gBAAgB,CAACC,OAAsB;AAC3C,QAAIA,KAAI,KAAK,OAAO,OAAOA,EAAC,MAAM,KAAQ,OAAOA,KAAI,CAAC,MAAM,GAAM;AAChE,UAAI,OAAOA,KAAI,CAAC,MAAM,EAAM,QAAO;AACnC,UAAIA,KAAI,KAAK,OAAO,OAAOA,KAAI,CAAC,MAAM,KAAQ,OAAOA,KAAI,CAAC,MAAM;AAC9D,eAAO;AAAA,IACX;AACA,WAAO;AAAA,EACT;AAEA,MAAI,IAAI;AAER,SAAO,IAAI,KAAK;AACd,UAAM,KAAK,cAAc,CAAC;AAC1B,QAAI,GAAI;AACR;AAAA,EACF;AAEA,SAAO,IAAI,KAAK;AACd,UAAM,KAAK,cAAc,CAAC;AAC1B,QAAI,CAAC,IAAI;AACP;AACA;AAAA,IACF;AACA,UAAM,WAAW,IAAI;AACrB,QAAI,IAAI;AACR,WAAO,IAAI,KAAK;AACd,YAAM,MAAM,cAAc,CAAC;AAC3B,UAAI,IAAK;AACT;AAAA,IACF;AACA,QAAI,WAAW,GAAG;AAChB,YAAM,MAAM,OAAO,SAAS,UAAU,CAAC;AACvC,UAAI,IAAI,SAAS,EAAG,MAAK,KAAK,GAAG;AAAA,IACnC;AACA,QAAI;AAAA,EACN;AAEA,SAAO;AACT;AAEA,SAAS,0BAA0B,MAAc,UAAU,IAAY;AACrE,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,QAAM,MAAM,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC;AACtD,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK,KAAK;AAChC,QAAI,KAAK,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,EAAM;AAC9C,QAAI,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AACjC,QAAI,KAAK,IAAI,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AAAA,EAC3D;AACA,SAAO;AACT;AAEA,SAAS,4BAA4B,MAAsB;AACzD,MAAI,KAAK,UAAU,KAAK,KAAK,CAAC,MAAM,KAAQ,KAAK,CAAC,MAAM,GAAM;AAC5D,QAAI,KAAK,CAAC,MAAM,EAAM,QAAO,KAAK,SAAS,CAAC;AAC5C,QAAI,KAAK,CAAC,MAAM,KAAQ,KAAK,CAAC,MAAM,EAAM,QAAO,KAAK,SAAS,CAAC;AAAA,EAClE;AACA,SAAO;AACT;AAEA,SAAS,0BACP,MACA,YACA,QACU;AAGV,QAAM,OAAiB,CAAC;AACxB,MAAI,SAAS;AAEb,QAAM,UAAU,CAAC,KAAa,OAAuB;AACnD,QAAI,eAAe;AACjB,aAAO,WAAW,OAAO,IAAI,aAAa,EAAE,IAAI,IAAI,aAAa,EAAE;AACrE,QAAI,eAAe;AACjB,aAAO,WAAW,OAAO,IAAI,aAAa,EAAE,IAAI,IAAI,aAAa,EAAE;AACrE,QAAI,eAAe,GAAG;AACpB,YAAM,KAAK,IAAI,EAAE;AACjB,YAAM,KAAK,IAAI,KAAK,CAAC;AACrB,YAAM,KAAK,IAAI,KAAK,CAAC;AACrB,aAAO,WAAW,QACZ,MAAM,KAAO,MAAM,IAAK,QAAQ,KAChC,MAAM,KAAO,MAAM,IAAK,QAAQ;AAAA,IACxC;AACA,WAAO,IAAI,UAAU,EAAE;AAAA,EACzB;AAEA,SAAO,SAAS,cAAc,KAAK,QAAQ;AACzC,UAAM,SAAS,QAAQ,MAAM,MAAM;AACnC,cAAU;AAGV,QAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAI,SAAS,SAAS,KAAK,OAAQ,QAAO,CAAC;AAE3C,UAAM,MAAM,KAAK,SAAS,QAAQ,SAAS,MAAM;AACjD,cAAU;AACV,QAAI,IAAI,OAAQ,MAAK,KAAK,GAAG;AAAA,EAC/B;AAGA,MAAI,WAAW,KAAK,OAAQ,QAAO,CAAC;AACpC,SAAO;AACT;AAEA,SAAS,UAAU,WAAkC,MAAwB;AAC3E,MAAI,CAAC,KAAK,OAAQ,QAAO,OAAO;AAEhC,MAAI,UAAU;AACd,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,IAAI,QAAQ;AACf;AACA;AAAA,IACF;AAGA,SAAK,IAAI,CAAC,IAAK,SAAU,GAAG;AAC1B,iBAAW;AACX;AAAA,IACF;AAEA,QAAI,cAAc,QAAQ;AACxB,YAAM,IAAI,IAAI,CAAC,IAAK;AAEpB,UAAI,IAAI,KAAK,IAAI,GAAI;AAAA,IACvB,WAAW,cAAc,QAAQ;AAC/B,UAAI,IAAI,SAAS,GAAG;AAClB;AACA;AAAA,MACF;AACA,YAAM,IAAK,IAAI,CAAC,KAAM,IAAK;AAG3B,YAAM,UACH,KAAK,KAAK,KAAK,MAAQ,KAAK,MAAM,KAAK,MAAQ,KAAK,MAAM,KAAK;AAClE,UAAI,CAAC,QAAS;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,gCACP,YACA,WACU;AACV,MAAI,CAAC,YAAY,OAAQ,QAAO,CAAC;AAEjC,QAAM,aAAyB,CAAC;AAIhC,QAAM,WAAW,0BAA0B,YAAY,EAAE;AACzD,MAAI,YAAY,GAAG;AACjB,UAAM,QAAQ,aAAa,IAAI,aAAa,WAAW,SAAS,QAAQ;AACxE,UAAM,OAAO,kBAAkB,KAAK;AACpC,QAAI,KAAK,OAAQ,YAAW,KAAK,IAAI;AAAA,EACvC;AAIA,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAG9D,QAAM,WAAW,4BAA4B,UAAU;AACvD,MAAI,SAAS,OAAQ,YAAW,KAAK,CAAC,QAAQ,CAAC;AAE/C,MAAI,OAAiB,CAAC;AACtB,MAAI,YAAY,OAAO;AACvB,aAAW,QAAQ,YAAY;AAC7B,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,IAAI,UAAU,WAAW,IAAI;AACnC,QAAI,IAAI,WAAW;AACjB,kBAAY;AACZ,aAAO;AACP,UAAI,cAAc,EAAG;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,mCAAmC,QAIjD;AACA,QAAM,OAAO,gCAAgC,QAAQ,MAAM;AAC3D,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,EAAG;AACpB,UAAM,UAAU,IAAI,CAAC,IAAK;AAC1B,QAAI,YAAY,GAAG;AACjB,YAAM;AACN,UAAI,IAAI,UAAU,GAAG;AACnB,cAAM,MAAM,OAAO,KAAK,CAAC,IAAI,CAAC,GAAI,IAAI,CAAC,GAAI,IAAI,CAAC,CAAE,CAAC,EAAE,SAAS,KAAK;AACnE,yBAAiB;AAAA,MACnB;AAAA,IACF,WAAW,YAAY,GAAG;AACxB,YAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,MAA+D,CAAC;AACtE,MAAI,IAAK,KAAI,MAAM;AACnB,MAAI,IAAK,KAAI,MAAM;AACnB,MAAI,eAAgB,KAAI,iBAAiB;AACzC,SAAO;AACT;AAEO,SAAS,mCAAmC,QAIjD;AACA,QAAM,OAAO,gCAAgC,QAAQ,MAAM;AAC3D,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,EAAG;AACpB,UAAM,UAAW,IAAI,CAAC,KAAM,IAAK;AACjC,QAAI,YAAY,GAAI,OAAM;AAAA,aACjB,YAAY,GAAI,OAAM;AAAA,aACtB,YAAY,GAAI,OAAM;AAAA,EACjC;AAEA,QAAM,MAAoD,CAAC;AAC3D,MAAI,IAAK,KAAI,MAAM;AACnB,MAAI,IAAK,KAAI,MAAM;AACnB,MAAI,IAAK,KAAI,MAAM;AACnB,SAAO;AACT;AAEA,IAAM,iBAAiB;AAAA,EACrB;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACtE;AAAA,EAAM;AACR;AAEO,SAAS,+BAA+B,QAIxB;AACrB,QAAM,EAAE,YAAY,SAAS,IAAI;AACjC,QAAM,kBAAkB,OAAO,mBAAmB;AAClD,QAAM,oBAAoB,eAAe,QAAQ,UAAU;AAC3D,MAAI,oBAAoB,EAAG;AAC3B,MAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,KAAK,WAAW,GAAI;AAClE,MACE,CAAC,OAAO,SAAS,eAAe,KAChC,mBAAmB,KACnB,kBAAkB;AAElB;AAGF,QAAM,OACF,kBAAkB,OAAS,MAC3B,oBAAoB,OAAS,KAC7B,WAAW,OAAS;AACxB,SAAO,IAAI,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACzC;AAEO,SAAS,gBAAgB,WAKvB;AACP,MAAI,UAAU,SAAS,EAAG,QAAO;AACjC,MAAI,UAAU,CAAC,MAAO,QAAS,UAAU,CAAC,IAAK,SAAU,IAAM,QAAO;AAEtE,QAAM,oBAAoB,UAAU,CAAC,IAAK,OAAU;AACpD,QAAM,WAAW,UAAU,CAAC,IAAK,QAAS;AAC1C,QAAM,kBAAkB,UAAU;AAClC,QAAM,qBAAqB,UAAU,CAAC,IAAK,OAAS;AACpD,QAAM,aAAa,eAAe,iBAAiB,KAAK;AACxD,QAAM,YACF,UAAU,CAAC,IAAK,MAAS,KAAO,UAAU,CAAC,IAAK,QAAS;AAE7D,MAAI,CAAC,cAAc,CAAC,SAAU,QAAO;AAErC,QAAM,eAAe,mBAAmB,IAAI;AAC5C,MAAI,UAAU,SAAS,aAAc,QAAO;AAG5C,QAAM,OACF,kBAAkB,OAAS,MAC3B,oBAAoB,OAAS,KAC7B,WAAW,OAAS;AACxB,QAAM,YAAY,IAAI,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAElD,SAAO,EAAE,cAAc,YAAY,UAAU,UAAU;AACzD;AAMA,IAAM,YAAN,MAAgB;AAAA,EAKd,YAAoB,aAAqB;AAArB;AAClB,SAAK,MAAM,OAAO,YAAY,CAAC,EAAE,aAAa,CAAC;AAG/C,SAAK,YAAY,OAAO,YAAY,CAAC,EAAE,aAAa,CAAC;AAAA,EACvD;AAAA,EATQ,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,OAAO,OAAO,YAAY,CAAC,EAAE,aAAa,CAAC;AAAA,EASnD,aAAa,IAAY;AACvB,SAAK,YAAY,OAAO;AAAA,EAC1B;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA,EAEA,iBAAiB,OAAe;AAC9B,SAAK,YAAa,KAAK,aAAa,UAAU,OAAQ;AAAA,EACxD;AAAA,EAEA,YAAY,SAAiB,QAAyB;AACpD,UAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,WAAO,CAAC,IAAI;AACZ,WAAO,CAAC,KAAK,SAAS,MAAO,KAAS,KAAK,cAAc;AACzD,WAAO,cAAc,KAAK,MAAM,OAAQ,CAAC;AACzC,WAAO,cAAc,KAAK,cAAc,GAAG,CAAC;AAC5C,WAAO,cAAc,KAAK,SAAS,GAAG,CAAC;AACvC,SAAK,MAAO,KAAK,MAAM,IAAK;AAC5B,WAAO,OAAO,OAAO,CAAC,QAAQ,OAAO,CAAC;AAAA,EACxC;AACF;AAEO,SAAS,cACd,KACA,KACA,MACA,cACA,WACU;AACV,QAAM,MAAM,KAAK;AACjB,QAAM,MAAgB,CAAC;AACvB,MAAI,IAAI,UAAU,KAAK;AACrB,QAAI,KAAK,IAAI,YAAY,KAAK,gBAAgB,SAAS,CAAC;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,IAAI,CAAC;AAClB,QAAM,IAAI,OAAO;AACjB,QAAM,MAAM,OAAO;AACnB,QAAM,OAAO,OAAO;AACpB,QAAM,cAAc,IAAI,MAAM;AAE9B,QAAM,OAAO,IAAI,SAAS,CAAC;AAC3B,MAAI,SAAS;AACb,SAAO,SAAS,KAAK,QAAQ;AAC3B,UAAM,YAAY,KAAK,SAAS;AAChC,UAAM,WAAW,KAAK,IAAI,WAAW,MAAM,CAAC;AAC5C,UAAM,QAAQ,WAAW;AACzB,UAAM,MAAM,SAAS,YAAY,KAAK;AACtC,UAAM,YACH,QAAQ,MAAO,MAAS,MAAM,KAAO,KAAS,OAAO;AACxD,UAAM,UAAU,OAAO,OAAO;AAAA,MAC5B,OAAO,KAAK,CAAC,aAAa,QAAQ,CAAC;AAAA,MACnC,KAAK,SAAS,QAAQ,SAAS,QAAQ;AAAA,IACzC,CAAC;AACD,QAAI,KAAK,IAAI,YAAY,SAAS,gBAAgB,aAAa,GAAG,CAAC;AACnE,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAEO,SAAS,cACd,KACA,KACA,MACA,cACA,WACU;AACV,QAAM,MAAM,KAAK;AACjB,QAAM,MAAgB,CAAC;AACvB,MAAI,IAAI,UAAU,KAAK;AACrB,QAAI,KAAK,IAAI,YAAY,KAAK,gBAAgB,SAAS,CAAC;AACxD,WAAO;AAAA,EACT;AAEA,MAAI,IAAI,SAAS,EAAG,QAAO;AAE3B,QAAM,aAAa,IAAI,CAAC;AACxB,QAAM,aAAa,IAAI,CAAC;AACxB,QAAM,UAAW,cAAc,IAAK;AAGpC,QAAM,eAAgB,aAAa,MAAS,MAAM;AAClD,QAAM,eAAe;AAErB,QAAM,OAAO,IAAI,SAAS,CAAC;AAC3B,MAAI,SAAS;AACb,SAAO,SAAS,KAAK,QAAQ;AAC3B,UAAM,YAAY,KAAK,SAAS;AAChC,UAAM,WAAW,KAAK,IAAI,WAAW,MAAM,CAAC;AAC5C,UAAM,QAAQ,WAAW;AACzB,UAAM,MAAM,SAAS,YAAY,KAAK;AACtC,UAAM,YACH,QAAQ,MAAO,MAAS,MAAM,KAAO,KAAS,UAAU;AAC3D,UAAM,UAAU,OAAO,OAAO;AAAA,MAC5B,OAAO,KAAK,CAAC,cAAc,cAAc,QAAQ,CAAC;AAAA,MAClD,KAAK,SAAS,QAAQ,SAAS,QAAQ;AAAA,IACzC,CAAC;AACD,QAAI,KAAK,IAAI,YAAY,SAAS,gBAAgB,aAAa,GAAG,CAAC;AACnE,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAEO,SAAS,sBACd,MACA,KAIA;AACA,QAAM,SAAS,gBAAgB,IAAI;AACnC,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,CAAC,EAAE;AAClC,QAAM,MAAM,KAAK,SAAS,OAAO,YAAY;AAC7C,MAAI,CAAC,IAAI,OAAQ,QAAO,EAAE,SAAS,CAAC,EAAE;AAGtC,QAAM,kBAAkB,OAAO,KAAK,CAAC,GAAM,EAAI,CAAC;AAChD,QAAM,SAAS,IAAI,SAAS;AAC5B,QAAM,WAAW,OAAO,MAAM,CAAC;AAC/B,WAAS,CAAC,IAAK,UAAU,IAAK;AAC9B,WAAS,CAAC,KAAK,SAAS,OAAS;AAEjC,QAAM,UAAU,OAAO,OAAO,CAAC,iBAAiB,UAAU,GAAG,CAAC;AAC9D,SAAO;AAAA,IACL,SAAS,CAAC,IAAI,YAAY,SAAS,IAAI,CAAC;AAAA,IACxC,QAAQ;AAAA,MACN,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,IACpB;AAAA,EACF;AACF;AAEO,SAAS,qBACd,KACA,KACuB;AACvB,MAAI,CAAC,KAAK,OAAQ,QAAO,EAAE,SAAS,CAAC,EAAE;AAGvC,QAAM,kBAAkB,OAAO,KAAK,CAAC,GAAM,EAAI,CAAC;AAChD,QAAM,SAAS,IAAI,SAAS;AAC5B,QAAM,WAAW,OAAO,MAAM,CAAC;AAC/B,WAAS,CAAC,IAAK,UAAU,IAAK;AAC9B,WAAS,CAAC,KAAK,SAAS,OAAS;AAEjC,QAAM,UAAU,OAAO,OAAO,CAAC,iBAAiB,UAAU,GAAG,CAAC;AAC9D,SAAO;AAAA,IACL,SAAS,CAAC,IAAI,YAAY,SAAS,IAAI,CAAC;AAAA,EAC1C;AACF;AAQO,IAAM,eAAN,MAAM,cAAa;AAAA,EAgDxB,YACU,QACA,kBACR,kBACA,mBAAmB,IACX,gBAAgB,MACxB;AALQ;AACA;AAGA;AAER,SAAK,WAAW,IAAI,UAAU,gBAAgB;AAC9C,QAAI,qBAAqB,QAAW;AAClC,WAAK,WAAW,IAAI,UAAU,gBAAgB;AAAA,IAChD;AACA,SAAK,yBAAyB,KAAK;AAAA,MACjC;AAAA,MACA,KAAK,MAAM,KAAK,iBAAiB,KAAK,IAAI,GAAG,gBAAgB,CAAC;AAAA,IAChE;AACA,SAAK,uBAAuB,KAAK;AAAA,MAC/B;AAAA,MACA,KAAK;AAAA,QACF,KAAK,yBAAyB,MAAa,KAAK;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAAA,EApEQ,UAAU,oBAAI,IAAmB;AAAA,EACjC,SAAS;AAAA,EAET;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA;AAAA,EACA,oBAAoB;AAAA,EACpB;AAAA,EACA;AAAA,EACA,8BAA8B;AAAA,EAC9B;AAAA,EACS,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA;AAAA,EACpB,WAAW;AAAA;AAAA,EAGpB,sBAOJ;AAAA,IACF,yBAAyB;AAAA,IACzB,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,WAAW;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EAEQ;AAAA,EACA;AAAA,EAyBR,UAAU,QAAoB;AAC5B,QAAI,KAAK,QAAQ;AACf,aAAO,QAAQ;AACf;AAAA,IACF;AAEA,UAAM,SAAwB;AAAA,MAC5B;AAAA,MACA,eAAe;AAAA,MACf,iBAAiB;AAAA,IACnB;AACA,SAAK,QAAQ,IAAI,MAAM;AAEvB,UAAM,UAAU,MAAM;AACpB,WAAK,QAAQ,OAAO,MAAM;AAC1B,UAAI;AACF,eAAO,QAAQ;AAAA,MACjB,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,WAAO,GAAG,SAAS,OAAO;AAC1B,WAAO,GAAG,SAAS,OAAO;AAAA,EAC5B;AAAA,EAEA,OAAwB,oBAAoB,OAAO,KAAK;AAAA,IACtD;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,EACpB,CAAC;AAAA,EAED,OAAe,kBACV,MACiB;AACpB,UAAM,UAAU,KAAK,OAAO,CAAC,MAAmB,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC;AACnE,QAAI,CAAC,QAAQ,OAAQ;AACrB,UAAM,QAAkB,CAAC;AACzB,eAAW,OAAO,SAAS;AACzB,YAAM,KAAK,cAAa,mBAAmB,GAAG;AAAA,IAChD;AACA,WAAO,OAAO,OAAO,KAAK;AAAA,EAC5B;AAAA,EAEQ,mCACN,WACA,YACM;AACN,QAAI,CAAC,YAAY,OAAQ;AAEzB,QAAI,cAAc,QAAQ;AACxB,YAAM,EAAE,KAAAC,MAAK,KAAAC,KAAI,IAAI,mCAAmC,UAAU;AAClE,UAAID,QAAOC,MAAK;AACd,aAAK,4BAA4B,cAAa,eAAeD,MAAKC,IAAG;AAAA,MACvE;AACA;AAAA,IACF;AAEA,UAAM,EAAE,KAAK,KAAK,IAAI,IAAI,mCAAmC,UAAU;AACvE,QAAI,OAAO,OAAO,KAAK;AACrB,WAAK,4BAA4B,cAAa;AAAA,QAC5C;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,8BACN,WACA,QACM;AACN,QAAI,KAAK,OAAQ;AACjB,QAAI,OAAO,gBAAiB;AAE5B,UAAM,kBACJ,cAAc,SACV,KAAK,4BACL,KAAK;AACX,QAAI,CAAC,iBAAiB,OAAQ;AAE9B,UAAM,OAAO,gCAAgC,iBAAiB,SAAS;AACvE,QAAI,CAAC,KAAK,OAAQ;AAElB,UAAM,OAAgC,EAAE,eAAe,KAAK,cAAc;AAC1E,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,MAAM,KAAK,CAAC;AAClB,YAAM,YAAY,MAAM,KAAK,SAAS;AACtC,YAAM,UACJ,cAAc,SACV,cAAc,KAAK,KAAK,UAAU,MAAM,OAAO,SAAS,IACxD,cAAc,KAAK,KAAK,UAAU,MAAM,OAAO,SAAS;AAE9D,iBAAW,OAAO,SAAS;AACzB,aAAK,uBAAuB,QAAQ,GAAG;AAAA,MACzC;AAAA,IACF;AAEA,WAAO,kBAAkB;AAAA,EAC3B;AAAA,EAEA,QAAQ;AACN,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,eAAW,KAAK,MAAM,KAAK,KAAK,OAAO,GAAG;AACxC,UAAI;AACF,UAAE,OAAO,QAAQ;AAAA,MACnB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA,EAEQ,uBAAuB,QAAuB,KAAa;AACjE,QAAI,OAAO,OAAO,aAAa,CAAC,OAAO,OAAO,SAAU;AAExD,UAAM,SAAS,OAAO,MAAM,CAAC;AAC7B,WAAO,cAAc,IAAI,SAAS,OAAQ,CAAC;AAC3C,UAAM,SAAS,OAAO,OAAO,CAAC,QAAQ,GAAG,CAAC;AAE1C,QAAI;AACF,aAAO,OAAO,MAAM,MAAM;AAAA,IAC5B,QAAQ;AACN,UAAI;AACF,eAAO,OAAO,QAAQ;AAAA,MACxB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,wBACN,KACA,WACA;AACA,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,CAAC,UAAU,CAAC,EAAG;AACnB,WAAK,uBAAuB,GAAG,GAAG;AAAA,IACpC;AAAA,EACF;AAAA,EAEQ,6BAAmC;AAEzC,SAAK,iBAAiB;AACtB,SAAK,oBAAoB;AACzB,SAAK,iBAAiB;AACtB,SAAK,kBAAkB;AACvB,SAAK,8BAA8B;AACnC,SAAK,6BAA6B;AAAA,EACpC;AAAA,EAEQ,eACN,MACA,SACM;AACN,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa;AACnB,UAAM,QAAQ,KAAK;AACnB,QAAI,SAAS,mBAAmB;AAC9B,YAAM;AACN,UAAI,MAAM,MAAM,0BAA0B,WAAY;AACtD,YAAM,0BAA0B;AAChC,WAAK,OAAO;AAAA,QACV,2BAA2B,OAAO,yBAAyB,MAAM,mBAAmB;AAAA,MACtF;AACA;AAAA,IACF;AACA,QAAI,SAAS,QAAQ;AACnB,YAAM;AACN,UAAI,MAAM,MAAM,gBAAgB,WAAY;AAC5C,YAAM,gBAAgB;AACtB,WAAK,OAAO;AAAA,QACV,2BAA2B,OAAO,eAAe,MAAM,SAAS;AAAA,MAClE;AACA;AAAA,IACF;AACA,UAAM;AACN,QAAI,MAAM,MAAM,iBAAiB,WAAY;AAC7C,UAAM,iBAAiB;AACvB,SAAK,OAAO;AAAA,MACV,2BAA2B,OAAO,gBAAgB,MAAM,UAAU;AAAA,IACpE;AAAA,EACF;AAAA,EAEQ,gCAAsC;AAC5C,SAAK,SAAS,iBAAiB,KAAK,sBAAsB;AAAA,EAC5D;AAAA,EAEA,kCACE,mBACA;AACA,QAAI,sBAAsB,QAAQ,sBAAsB,QAAW;AACjE,WAAK,8BAA8B;AACnC;AAAA,IACF;AACA,QAAI,CAAC,OAAO,SAAS,iBAAiB,GAAG;AACvC,WAAK,8BAA8B;AACnC;AAAA,IACF;AAEA,UAAM,WAAY,sBAAsB;AAExC,QAAI,KAAK,mBAAmB,QAAW;AACrC,YAAM,YAAY,KAAK;AACvB,UAAI,WAAW,WAAW;AAExB,cAAM,aAAa,YAAY,cAAc,WAAW;AACxD,YAAI,YAAY;AACd,eAAK,qBAAqB;AAC1B,eAAK;AAAA,YACH;AAAA,YACA,gCAAgC,SAAS,aAAa,QAAQ,eAAe,KAAK,iBAAiB;AAAA,UACrG;AAAA,QACF,OAAO;AAEL,eAAK;AAAA,YACH;AAAA,YACA,yDAAyD,SAAS,aAAa,QAAQ;AAAA,UACzF;AACA,eAAK,2BAA2B;AAAA,QAClC;AAAA,MACF;AAAA,IACF;AAEA,SAAK,iBAAiB;AACtB,UAAM,QAAQ,KAAK,oBAAoB;AAEvC,QAAI,KAAK,mBAAmB,QAAW;AACrC,WAAK,iBAAiB;AACtB,UAAI,KAAK,oBAAoB;AAC3B,aAAK,kBAAkB,KAAK;AAC9B;AAAA,IACF;AAEA,UAAM,YAAY,KAAK;AACvB,UAAM,UAAU,QAAQ;AACxB,SAAK,iBAAiB;AAWtB,QAAI;AACJ,QAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG;AAC5C,WAAK;AAAA,QACH;AAAA,QACA,qBAAqB,OAAO,WAAW,KAAK,cAAc,SAAS,eAAe,KAAK,mBAAmB,KAAK;AAAA,MACjH;AACA,WAAK,8BAA8B;AACnC,yBACE,KAAK,8BAA8B,KAAK;AAAA,IAC5C,WAAW,YAAY,GAAG;AACxB,WAAK;AAEL,yBACE,KAAK,8BAA8B,KAAK;AAAA,IAC5C,WAAW,WAAW,KAAK,mBAAmB;AAC5C,UAAI,KAAK,8BAA8B,GAAG;AACxC,cAAM,gBAAgB,KAAK,8BAA8B;AACzD,cAAM,kBAAkB,KAAK;AAAA,UAC3B;AAAA,UACA,KAAK,MAAM,UAAU,aAAa;AAAA,QACpC;AACA,aAAK,6BAA6B;AAClC,aAAK,8BAA8B;AAEnC,cAAM,UAAU,KAAK,mBAAmB;AACxC,aAAK,kBACH,WAAW,kBAAkB,WAAW,KAAK;AAC/C,2BAAmB;AAAA,MACrB,OAAO;AACL,cAAM,UAAU,KAAK,mBAAmB;AACxC,aAAK,kBAAkB,WAAW,UAAU,WAAW,KAAK;AAC5D,2BAAmB;AAAA,MACrB;AAAA,IACF,OAAO;AAEL,WAAK;AAAA,QACH;AAAA,QACA,qBAAqB,OAAO,WAAW,KAAK,cAAc,SAAS,eAAe,KAAK,mBAAmB,KAAK,cAAc,KAAK,2BAA2B;AAAA,MAC/J;AACA,WAAK,8BAA8B;AACnC,yBACE,KAAK,8BAA8B,KAAK;AAAA,IAC5C;AAEA,UAAM,MAAM,KAAK;AAAA,MACf;AAAA,MACA,KAAK,MAAO,mBAAmB,KAAK,iBAAkB,GAAS;AAAA,IACjE;AACA,SAAK,SAAS,iBAAiB,GAAG;AAAA,EACpC;AAAA,EAEA,oBACE,WACA,kBACA,YACA,cACA;AACA,QAAI,KAAK,OAAQ;AAIjB,SAAK,mCAAmC,WAAW,gBAAgB;AAEnE,UAAM,OAAO,gCAAgC,kBAAkB,SAAS;AACxE,QAAI,CAAC,KAAK,OAAQ;AAIlB,QAAI,kBAAkB;AACtB,QAAI,cAAc,QAAQ;AACxB,iBAAW,OAAO,MAAM;AACtB,cAAM,IAAI,IAAI,CAAC,IAAK;AACpB,YAAI,MAAM,GAAG;AAEX,4BAAkB;AAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,OAAO,MAAM;AACtB,YAAI,IAAI,SAAS,EAAG;AACpB,cAAM,IAAK,IAAI,CAAC,KAAM,IAAK;AAE3B,YAAI,KAAK,MAAM,KAAK,IAAI;AACtB,4BAAkB;AAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,oBAAoB,cAAc;AAIxC,UAAM,eAAe,CAAC,MACpB,oBAAoB,OAAO,CAAC,EAAE;AAChC,QAAI,eAAe;AACnB,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,aAAa,CAAC,GAAG;AACnB,uBAAe;AACf;AAAA,MACF;AAAA,IACF;AAGA,QAAI,CAAC,cAAc;AACjB,iBAAW,KAAK,KAAK,SAAS;AAC5B,YAAI,CAAC,EAAE,cAAe;AACtB,aAAK,8BAA8B,WAAW,CAAC;AAAA,MACjD;AACA;AAAA,IACF;AAEA,SAAK,kCAAkC,YAAY;AAGnD,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,CAAC,EAAE,cAAe;AACtB,WAAK,8BAA8B,WAAW,CAAC;AAAA,IACjD;AAEA,UAAM,OAAgC,EAAE,eAAe,KAAK,cAAc;AAC1E,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,MAAM,KAAK,CAAC;AAClB,YAAM,YAAY,MAAM,KAAK,SAAS;AACtC,YAAM,UACJ,cAAc,SACV,cAAc,KAAK,KAAK,UAAU,MAAM,MAAM,SAAS,IACvD,cAAc,KAAK,KAAK,UAAU,MAAM,MAAM,SAAS;AAE7D,iBAAW,OAAO;AAChB,aAAK,wBAAwB,KAAK,YAAY;AAAA,IAClD;AAGA,QAAI,mBAAmB;AACrB,iBAAW,KAAK,KAAK,QAAS,GAAE,gBAAgB;AAAA,IAClD;AAAA,EACF;AAAA,EAEA,mBAAmB,MAEjB;AACA,QAAI,KAAK,OAAQ,QAAO,CAAC;AACzB,QAAI,CAAC,KAAK,SAAU,QAAO,CAAC;AAE5B,UAAM,EAAE,SAAS,OAAO,IAAI,sBAAsB,MAAM,KAAK,QAAQ;AAErE,eAAW,OAAO;AAChB,WAAK,wBAAwB,KAAK,CAAC,MAAM,CAAC,EAAE,aAAa;AAG3D,QAAI,QAAQ,OAAQ,MAAK,SAAS,iBAAiB,IAAI;AAEvD,WAAO,SAAS,EAAE,QAAQ,OAAO,IAAI,CAAC;AAAA,EACxC;AAAA,EAEA,qBAAqB,KAAmB;AACtC,QAAI,KAAK,OAAQ;AACjB,QAAI,CAAC,KAAK,SAAU;AAEpB,UAAM,EAAE,QAAQ,IAAI,qBAAqB,KAAK,KAAK,QAAQ;AAE3D,eAAW,OAAO;AAChB,WAAK,wBAAwB,KAAK,CAAC,MAAM,CAAC,EAAE,aAAa;AAG3D,QAAI,QAAQ,OAAQ,MAAK,SAAS,iBAAiB,IAAI;AAAA,EACzD;AAAA,EAEA,mBAAmB,WAAyB;AAC1C,QAAI,KAAK,OAAQ;AACjB,QAAI,CAAC,aAAa,UAAU,SAAS,GAAI;AACzC,UAAM,UAAW,UAAU,CAAC,KAAM,IAAK;AACvC,QAAI,YAAY,EAAG;AAGnB,SAAK,wBAAwB,WAAW,CAAC,MAAM,CAAC,EAAE,aAAa;AAAA,EACjE;AAAA,EAEA,OAAe,gBAAgB,QAAoC;AACjE,QAAI,CAAC,UAAU,OAAO,SAAS,GAAI;AACnC,UAAM,UAAW,OAAO,CAAC,KAAM,IAAK;AACpC,QAAI,YAAY,EAAG;AAEnB,UAAM,WAAW,OAAO,CAAC,IAAK,QAAU;AACxC,UAAM,aAAa,OAAO,CAAC,IAAK,QAAU;AAC1C,UAAM,YAAY,OAAO,CAAC,IAAK;AAC/B,QAAI,SAAS,KAAK,YAAY;AAC9B,QAAI,SAAS,OAAO,OAAQ;AAE5B,QAAI,WAAW;AACb,UAAI,SAAS,IAAI,OAAO,OAAQ;AAChC,YAAM,cAAc,OAAO,aAAa,SAAS,CAAC;AAClD,gBAAU,IAAI,cAAc;AAC5B,UAAI,SAAS,OAAO,OAAQ;AAAA,IAC9B;AAEA,QAAI,MAAM,OAAO;AACjB,QAAI,SAAS;AACX,YAAM,SAAS,OAAO,OAAO,SAAS,CAAC;AACvC,UAAI,UAAU,KAAK,SAAS,OAAO,OAAQ;AAC3C,YAAM,OAAO,SAAS;AACtB,UAAI,MAAM,OAAQ;AAAA,IACpB;AAEA,QAAI,OAAO,OAAQ;AACnB,WAAO,OAAO,SAAS,QAAQ,GAAG;AAAA,EACpC;AAAA,EAEA,OAAe,4BAA4B,QAAyB;AAClE,UAAM,UAAU,cAAa,gBAAgB,MAAM;AACnD,QAAI,CAAC,WAAW,QAAQ,SAAS,EAAG,QAAO;AAE3C,UAAM,UAAU,QAAQ,CAAC,IAAK;AAG9B,QAAI,WAAW,KAAK,WAAW,IAAI;AACjC,aAAO,YAAY,KAAK,YAAY,KAAK,YAAY;AAAA,IACvD;AAGA,QAAI,YAAY,IAAI;AAClB,UAAI,SAAS;AACb,aAAO,SAAS,KAAK,QAAQ,QAAQ;AACnC,cAAM,OAAO,QAAQ,aAAa,MAAM;AACxC,kBAAU;AACV,YAAI,CAAC,QAAQ,SAAS,OAAO,QAAQ,OAAQ;AAC7C,cAAM,IAAI,QAAQ,MAAM,IAAK;AAC7B,YAAI,MAAM,KAAK,MAAM,KAAK,MAAM,EAAG,QAAO;AAC1C,kBAAU;AAAA,MACZ;AACA,aAAO;AAAA,IACT;AAGA,QAAI,YAAY,MAAM,QAAQ,UAAU,GAAG;AACzC,YAAM,WAAW,QAAQ,CAAC;AAC1B,YAAM,SAAS,WAAW,SAAU;AACpC,YAAM,WAAW,WAAW;AAC5B,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO,aAAa,KAAK,aAAa,KAAK,aAAa;AAAA,IAC1D;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,OAAe,4BAA4B,QAAyB;AAClE,UAAM,UAAU,cAAa,gBAAgB,MAAM;AACnD,QAAI,CAAC,WAAW,QAAQ,SAAS,EAAG,QAAO;AAE3C,UAAM,UAAW,QAAQ,CAAC,KAAM,IAAK;AAErC,UAAM,eAAe,CAAC,MAAc;AAElC,UAAI,KAAK,MAAM,KAAK,GAAI,QAAO;AAC/B,UAAI,MAAM,MAAM,MAAM,MAAM,MAAM,GAAI,QAAO;AAC7C,aAAO;AAAA,IACT;AAGA,QAAI,YAAY,MAAM,QAAQ,UAAU,GAAG;AACzC,YAAM,WAAW,QAAQ,CAAC;AAC1B,YAAM,SAAS,WAAW,SAAU;AACpC,YAAM,WAAW,WAAW;AAC5B,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO,aAAa,QAAQ;AAAA,IAC9B;AAEA,WAAO,aAAa,OAAO;AAAA,EAC7B;AAAA,EAEA,mBAAmB,WAAsB,WAAyB;AAChE,QAAI,KAAK,OAAQ;AACjB,QAAI,CAAC,aAAa,UAAU,SAAS,GAAI;AACzC,UAAM,UAAW,UAAU,CAAC,KAAM,IAAK;AACvC,QAAI,YAAY,EAAG;AAEnB,UAAM,iBACJ,cAAc,SACV,cAAa,4BAA4B,SAAS,IAClD,cAAa,4BAA4B,SAAS;AAExD,UAAM,eAAe,CAAC,MACpB,iBAAiB,OAAO,CAAC,EAAE;AAC7B,SAAK,wBAAwB,WAAW,YAAY;AAEpD,QAAI,gBAAgB;AAClB,iBAAW,KAAK,KAAK,QAAS,GAAE,gBAAgB;AAAA,IAClD;AAAA,EACF;AACF;;;AC3pCA,OAAO,aAAa;;;ACQpB,SAAS,SAAAC,cAAa;AACtB,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAkH7B,SAAS,yBACP,UACA,WACA,YACA,UACA,WACA,QAC0B;AAC1B,QAAM,OAAO,KAAK,MAAM,QAAQ;AAChC,QAAM,OAAO,KAAK,MAAM,SAAS;AACjC,QAAM,IAAI;AAEV,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,IACtB,KAAK;AACH,aAAO,EAAE,GAAG,YAAY,OAAO,GAAG,GAAG,EAAE;AAAA,IACzC,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,GAAG,aAAa,OAAO,EAAE;AAAA,IAC1C,KAAK;AACH,aAAO,EAAE,GAAG,YAAY,OAAO,GAAG,GAAG,aAAa,OAAO,EAAE;AAAA,IAC7D,KAAK;AACH,aAAO,EAAE,GAAG,KAAK,OAAO,YAAY,QAAQ,CAAC,GAAG,GAAG,KAAK,OAAO,aAAa,QAAQ,CAAC,EAAE;AAAA,IACzF,KAAK;AACH,aAAO,EAAE,GAAG,KAAK,OAAO,YAAY,QAAQ,CAAC,GAAG,GAAG,EAAE;AAAA,IACvD,KAAK;AACH,aAAO,EAAE,GAAG,KAAK,OAAO,YAAY,QAAQ,CAAC,GAAG,GAAG,aAAa,OAAO,EAAE;AAAA,IAC3E,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,GAAG,KAAK,OAAO,aAAa,QAAQ,CAAC,EAAE;AAAA,IACxD,KAAK;AACH,aAAO,EAAE,GAAG,YAAY,OAAO,GAAG,GAAG,KAAK,OAAO,aAAa,QAAQ,CAAC,EAAE;AAAA,IAC3E;AACE,aAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,EACxB;AACF;AAKO,IAAM,kBAAN,cAA8B,aAKlC;AAAA,EACO;AAAA,EACA,cAAyD;AAAA,EACzD,aAAwD;AAAA,EACxD,gBAAiD;AAAA,EACjD,SAAS;AAAA,EACT;AAAA,EAEA,YAA+B;AAAA;AAAA;AAAA,EAI/B,eAAuB,OAAO,MAAM,CAAC;AAAA;AAAA,EAGrC;AAAA,EAGA;AAAA,EAIA;AAAA,EAEA,0BAA0B;AAAA,EAC1B,uBAAuB;AAAA,EAEvB;AAAA,EAEA;AAAA,EACA;AAAA,EAEA,gBAAgB,UAAe,WAAyC;AAC9E,QAAI;AACF,YAAM,SAAS,CAAC,SAAkB,OAAO,IAAI,YAAY,EAAE,SAAS,KAAK;AACzE,YAAM,UAAiB,MAAM,QAAQ,UAAU,OAAO,IAAI,SAAS,UAAU,CAAC;AAC9E,YAAM,YAAY,oBAAI,IAAiB;AACvC,iBAAW,KAAK,QAAS,WAAU,IAAI,EAAE,SAAS,CAAC;AAEnD,UAAI,OAAO,UAAU,IAAI,SAAS,GAAG,YAAY,EAAG,QAAO;AAE3D,YAAM,iBAAkC,CAAC,OAAO,QAAQ,KAAK;AAC7D,iBAAW,KAAK,gBAAgB;AAC9B,cAAM,KAAK,UAAU,IAAI,CAAC;AAC1B,YAAI,MAAM,OAAO,GAAG,YAAY,EAAG,QAAO;AAAA,MAC5C;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,KAA+D;AACpF,QAAI,CAAC,IAAK,QAAO,QAAQ,QAAQ;AACjC,UAAM,IAAK,IAAY;AACvB,QAAI,OAAO,MAAM,WAAY,QAAO,QAAQ,QAAQ;AACpD,QAAI;AACF,aAAO,QAAQ,QAAQ,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,MAAM,MAAS,EAAE,MAAM,MAAM,MAAS;AAAA,IACjF,QAAQ;AACN,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,YAAY,SAAiC;AAC3C,UAAM;AACN,SAAK,UAAU;AAAA,MACb,aAAa;AAAA,MACb,SAAS;AAAA,MACT,WAAW;AAAA,MACX,GAAG;AAAA,IACL;AACA,SAAK,SAAS,QAAQ,UAAU;AAAA,EAClC;AAAA,EAEQ,mBAAmB,WAAmB,YAA4B;AACxE,UAAM,MAAM,KAAK,QAAQ;AACzB,QAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO;AAC9C,UAAM,IAAI,OAAO,GAAG;AACpB,QAAI,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,EAAG,QAAO;AAEzC,QAAI,IAAI,EAAG,QAAO,KAAK,MAAM,CAAC;AAE9B,UAAM,OAAO,KAAK,IAAI,WAAW,UAAU;AAC3C,WAAO,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,CAAC;AAAA,EACzC;AAAA,EAEQ,kBAAkB,KAAwG;AAChI,UAAM,IAAS,MAAO,IAAY,SAAS;AAC3C,QAAI,CAAC,EAAG,QAAO,CAAC;AAChB,QAAI;AACF,YAAM,YAAY,OAAO,EAAE,iBAAiB,aAAa,EAAE,aAAa,IAAI;AAC5E,YAAM,OAAO,OAAO,EAAE,YAAY,aAAa,EAAE,QAAQ,IAAI;AAC7D,YAAM,MAAM,OAAO,EAAE,uBAAuB,aAAa,EAAE,mBAAmB,IAAI;AAClF,YAAM,MAAM,OAAO,EAAE,gBAAgB,aAAa,EAAE,YAAY,IAAI;AACpE,aAAO,EAAE,WAAW,MAAM,KAAK,IAAI;AAAA,IACrC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEQ,iBAAiB,KAAyF;AAChH,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,OAAO,IAAI,SAAS,GAAG,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,EAAE,SAAS,KAAK;AACrE,UAAM,QAAQ,IAAI,SAAS,GAAG,KAAK,IAAI,KAAK,IAAI,MAAM,CAAC;AACvD,UAAM,OAAO,WAAW,MAAM,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AAC1D,WAAO,EAAE,KAAK,IAAI,QAAQ,SAAS,MAAM,UAAU,KAAK;AAAA,EAC1D;AAAA,EAEA,MAAc,eACZ,KACA,WACA,iBACwH;AACxH,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI;AAEJ,WAAO,KAAK,IAAI,IAAI,QAAQ,WAAW;AACrC,YAAM,IAAI,MAAM,QAAQ,KAAK;AAAA,QAC3B,IAAI,KAAK;AAAA,QACT,IAAI,QAA6B,CAAC,YAAY,WAAW,MAAM,QAAQ,EAAE,OAAO,QAAW,MAAM,MAAM,CAAQ,GAAG,GAAG,CAAC;AAAA,MACxH,CAAC;AAED,UAAI,CAAC,KAAM,EAAU,KAAM,QAAO;AAClC,YAAM,IAAK,EAAU;AACrB,UAAI,CAAC,KAAK,EAAE,MAAO;AACnB,UAAI,CAAC,MAAO,SAAQ,EAAE,MAAM,EAAE,MAAM,WAAW,EAAE,WAAW,YAAY,EAAE,YAAY,cAAc,EAAE,aAAa;AACnH,UAAI,EAAE,WAAY,QAAO,EAAE,MAAM,EAAE,MAAM,WAAW,EAAE,WAAW,YAAY,EAAE,YAAY,cAAc,EAAE,aAAa;AAAA,IAC1H;AAEA,WAAO,kBAAkB,SAAY;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,iCAAiC;AAAA,IACnD;AAEA,SAAK,SAAS;AACd,SAAK,cAAc;AACnB,SAAK,uBAAuB;AAC5B,SAAK,OAAO,MAAM,gDAAgD;AAElE,QAAI;AACF,YAAM,WAAW,KAAK,QAAQ,YAAY,KAAK,QAAQ;AACvD,YAAM,UAAU,KAAK,QAAQ,WAAW,KAAK,QAAQ;AAErD,YAAM,kBAAkB,KAAK,kBAAkB,QAAQ;AACvD,YAAM,iBAAiB,KAAK,kBAAkB,OAAO;AACrD,YAAM,aAAc,UAAkB,UAAW,SAAiB,SAAU,SAAiB,WAAY,QAAgB,SAAS;AAClI,WAAK,OAAO;AAAA,QACV,sCAAsC,KAAK,QAAQ,YAAY,aAAa,KAAK,QAAQ,YAAY,aAAa,KAAK,QAAQ,WAAW,aAAa,KAAK,QAAQ,WAAW,WACpK,QAAQ,KAAK,QAAQ,KAAK,CAAC,cAAc,QAAQ,KAAK,QAAQ,SAAS,CAAC,eACnE,UAAU,gBACT,KAAK,UAAU,eAAe,CAAC,eAAe,KAAK,UAAU,cAAc,CAAC;AAAA,MAC/F;AAGA,YAAM,gBAAgB,MAAM,SAAS,kBAAkB,KAAK,QAAQ,YAAY;AAChF,YAAM,eAAe,MAAM,QAAQ,kBAAkB,KAAK,QAAQ,WAAW;AAE7E,YAAM,YAAY,KAAK,QAAQ,cAAc;AAC7C,YAAM,eAAe,YACjB,KAAK,gBAAgB,eAAe,KAAK,QAAQ,YAAY,IAC7D,KAAK,QAAQ;AACjB,YAAM,cAAc,YAChB,KAAK,gBAAgB,cAAc,KAAK,QAAQ,WAAW,IAC3D,KAAK,QAAQ;AAEjB,WAAK,wBAAwB;AAC7B,WAAK,uBAAuB;AAE5B,YAAM,kBAAkB,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,YAAY;AACpF,YAAM,iBAAiB,aAAa,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,WAAW;AAEjF,UAAI,CAAC,mBAAmB,CAAC,gBAAgB;AACvC,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC7C;AAEA,YAAM,YAAY,gBAAgB;AAClC,YAAM,aAAa,gBAAgB;AAGnC,YAAM,sBAAsB,KAAK,QAAQ,WAAW;AACpD,YAAM,UAAU,KAAK,IAAI,KAAK,KAAK,IAAI,MAAM,OAAO,mBAAmB,CAAC,CAAC;AACzE,YAAM,aAAa,eAAe,QAAQ,KAAK,eAAe,SAAS,IACnE,eAAe,QAAQ,eAAe,SACtC,KAAK;AAET,UAAI,WAAW,KAAK,MAAM,YAAY,OAAO;AAC7C,UAAI,YAAY,KAAK,MAAM,WAAW,UAAU;AAGhD,YAAM,eAAe,KAAK,MAAM,aAAa,OAAO;AACpD,UAAI,YAAY,cAAc;AAC5B,oBAAY;AACZ,mBAAW,KAAK,MAAM,YAAY,UAAU;AAAA,MAC9C;AAGA,iBAAW,KAAK,IAAI,GAAG,WAAY,WAAW,CAAE;AAChD,kBAAY,KAAK,IAAI,GAAG,YAAa,YAAY,CAAE;AAEnD,YAAM,WAAW;AAAA,QACf,KAAK,QAAQ,eAAe;AAAA,QAC5B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,mBAAmB,WAAW,UAAU;AAAA,MAC/C;AAEA,WAAK,OAAO;AAAA,QACV,2BAA2B,SAAS,IAAI,UAAU,UAAU,QAAQ,IAAI,SAAS,aACnE,OAAO,cAAc,KAAK,QAAQ,eAAe,cAAc,YAAY,KAAK,QAAQ,aAAa,EAAE,SAC5G,SAAS,CAAC,KAAK,SAAS,CAAC;AAAA,MACpC;AAEA,YAAM,eAAe,KAAK,QAAQ;AAClC,YAAM,cAAc,KAAK,QAAQ;AAEjC,UAAI,gBAAgB,aAAa;AAC/B,aAAK,YAAY;AAIjB,YAAI,CAAC,KAAK,QAAQ,kBAAkB;AAClC,gBAAM,SAAS,CAAC,SAAkB,OAAO,IAAI,YAAY,EAAE,SAAS,KAAK;AACzE,gBAAM,WAAW,iBAAiB;AAClC,gBAAM,UAAU,gBAAgB;AAChC,cAAI,CAAC,OAAO,QAAQ,KAAK,CAAC,OAAO,OAAO,GAAG;AACzC,kBAAM,IAAI;AAAA,cACR,qEACoB,YAAY,SAAS,SAAS,WAAW,SAAS;AAAA,YAExE;AAAA,UACF;AAAA,QACF;AAEA,cAAM,KAAK;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,QAAQ,iBAAiB;AAAA,QAChC;AAEA,aAAK,OAAO,MAAM,0DAA0D;AAC5E;AAAA,MACF;AAEA,WAAK,YAAY;AAKjB,YAAM,6BACJ,CAAC,CAAC,KAAK,QAAQ,SAAS,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;AAGpE,WAAK,cAAc,mBAAmB,UAAU,KAAK,QAAQ,cAAc,YAAY;AACvF,WAAK,aAAa,6BACd,mBAAmB,SAAS,KAAK,QAAQ,aAAa,aAAa,EAAE,SAAS,YAAY,CAAC,IAC3F,mBAAmB,SAAS,KAAK,QAAQ,aAAa,WAAW;AAKrE,YAAM,wBAAwB,KAAK,IAAI,KAAM,KAAK,QAAQ,yBAAyB,GAAM;AAEzF,YAAM,2BAA2B,KAAK,QAAQ,mBAAmB,QAAS,KAAK,QAAQ,4BAA4B;AAInH,YAAM,kBAAkB;AACxB,YAAM,kBAAkB,KAAK,IAAI,KAAO,KAAK,IAAI,KAAO,KAAK,MAAM,wBAAwB,CAAC,CAAC,CAAC;AAE9F,UAAI,0BAA0B;AAC5B,cAAM,CAAC,YAAY,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,UAChD,KAAK,eAAe,KAAK,aAAa,iBAAiB,IAAI;AAAA,UAC3D,KAAK,eAAe,KAAK,YAAY,iBAAiB,IAAI;AAAA,QAC5D,CAAC;AACD,aAAK,kBAAkB;AACvB,aAAK,iBAAiB;AAAA,MACxB,OAAO;AACL,cAAM,CAAC,YAAY,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,UAChD,KAAK,eAAe,KAAK,aAAa,iBAAiB,KAAK;AAAA,UAC5D,KAAK,eAAe,KAAK,YAAY,iBAAiB,KAAK;AAAA,QAC7D,CAAC;AACD,aAAK,kBAAkB;AACvB,aAAK,iBAAiB;AAAA,MACxB;AAEA,YAAM,UAAU,KAAK,iBAAiB,KAAK,iBAAiB,IAAI;AAChE,YAAM,SAAS,KAAK,iBAAiB,KAAK,gBAAgB,IAAI;AAC9D,UAAI,WAAW,QAAQ;AACrB,cAAM,OAAO,QAAQ,aAAa,OAAO;AACzC,aAAK,OAAO;AAAA,UACV,+CAA+C,KAAK,UAAU,OAAO,CAAC,SAAS,KAAK,UAAU,MAAM,CAAC,SAAS,IAAI;AAAA,QACpH;AACA,YAAI,MAAM;AACR,eAAK,OAAO;AAAA,YACV;AAAA,UAEF;AAAA,QACF;AAAA,MACF;AAEA,WAAK,OAAO;AAAA,QACV,kCAAkC,KAAK,iBAAiB,aAAa,aAAc,KAAK,kBAAkB,UAAU,MAAO,UAAU,KAAK,gBAAgB,aAAa,aAAc,KAAK,iBAAiB,UAAU,MAAO;AAAA,MAC9N;AAIA,UAAI,CAAC,KAAK,mBAAmB,CAAC,KAAK,gBAAgB;AACjD,cAAM,UAAU;AAAA,UACd,CAAC,KAAK,kBAAkB,UAAU;AAAA,UAClC,CAAC,KAAK,iBAAiB,SAAS;AAAA,QAClC,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC3B,cAAM,IAAI;AAAA,UACR,2CAA2C,OAAO,YAAY,qBAAqB;AAAA,QAGrF;AAAA,MACF;AAIA,UAAI,CAAC,KAAK,QAAQ,kBAAkB;AAClC,YAAI;AACF,gBAAM,MAAM,KAAK,gBAAgB;AACjC,gBAAM,MAAM,KAAK,eAAe;AAChC,cAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,KAAK,OAAO,SAAS,GAAG,GAAG;AACtG,kBAAM,YAAY,MAAM,OAAO;AAC/B,kBAAM,MAAM,KAAK,IAAI,QAAQ;AAE7B,gBAAI,OAAO,OAAO,OAAO,IAAI;AAC3B,kBAAI,WAAW,GAAG;AAEhB,qBAAK,uBAAuB,EAAE,OAAO,IAAI;AAAA,cAC3C,OAAO;AAEL,qBAAK,uBAAuB,EAAE,MAAM,IAAI;AAAA,cAC1C;AACA,mBAAK,OAAO;AAAA,gBACV,oDAAoD,GAAG,WAAW,GAAG,aAAa,SAAS,QAAQ,CAAC,CAAC,sBAC9E,IAAI,QAAQ,CAAC,CAAC,QAAQ,WAAW,IAAI,UAAU,MAAM;AAAA,cAC9E;AAAA,YACF;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF,OAAO;AACL,aAAK,uBAAuB;AAAA,MAC9B;AAEA,YAAM,uBAAuB,KAAK,iBAAiB,cAAc,SAAS,SAAU,KAAK,iBAAiB,cAAc,SAAS,SAAS;AAC1I,YAAM,sBAAsB,KAAK,gBAAgB,cAAc,SAAS,SAAU,KAAK,gBAAgB,cAAc,SAAS,SAAS;AAGvI,YAAM,KAAK,uBAAuB,WAAW,YAAY,UAAU,WAAW,UAAU,sBAAsB,qBAAqB,KAAK,oBAAoB;AAE5J,WAAK,OAAO,MAAM,4CAA4C;AAAA,IAChE,SAAS,OAAO;AACd,WAAK,SAAS;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,mCACZ,WACA,YACA,UACA,WACA,UACA,cACA,aACA,eACe;AAEf,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MAAa;AAAA,MACb;AAAA,MAAW;AAAA;AAAA,MAGX;AAAA,MAAmB;AAAA,MACnB;AAAA,MAAM;AAAA;AAAA,MAGN;AAAA,MAAmB;AAAA,MACnB;AAAA,MAAM;AAAA;AAAA,MAGN;AAAA,MACA,cAAc,SAAS,IAAI,UAAU,qBAAqB,QAAQ,IAAI,SAAS,4BAA4B,SAAS,CAAC,IAAI,SAAS,CAAC;AAAA,MACnI;AAAA,MAAQ;AAAA;AAAA,MAGR;AAAA,MACA;AAAA,MAAQ;AAAA,MACR;AAAA,MAAM;AAAA,MACN;AAAA,MAAe;AAAA,MACf;AAAA,MAAiB;AAAA,MACjB;AAAA,MAAgB;AAAA,MAChB;AAAA,MAAW;AAAA,MACX;AAAA,MAAS;AAAA,MACT;AAAA,MAAQ;AAAA,MACR;AAAA,MAAM;AAAA,MACN;AAAA,IACF;AAEA,SAAK,OAAO,MAAM,oDAAoD,WAAW,KAAK,GAAG,CAAC,EAAE;AAE5F,SAAK,gBAAgBC,OAAM,UAAU,YAAY;AAAA,MAC/C,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,UAAU;AACxC,WAAK,OAAO,QAAQ,mCAAmC,KAAK;AAC5D,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,SAAS;AACvC,UAAI,SAAS,KAAK,SAAS,MAAM;AAC/B,aAAK,OAAO,OAAO,6CAA6C,IAAI,EAAE;AAAA,MACxE;AACA,WAAK,KAAK,OAAO;AAAA,IACnB,CAAC;AAED,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,WAAK,mBAAmB,IAAI;AAAA,IAC9B,CAAC;AAED,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,YAAM,SAAS,KAAK,SAAS;AAC7B,UAAI,OAAO,SAAS,OAAO,KAAK,OAAO,SAAS,OAAO,GAAG;AACxD,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,KAAK,0BAA0B,KAAM;AAC7C,eAAK,0BAA0B;AAC/B,eAAK,uBAAuB;AAAA,QAC9B;AACA,YAAI,KAAK,yBAAyB,GAAG;AACnC,eAAK,OAAO,QAAQ,oCAAoC,MAAM;AAAA,QAChE;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,uBACZ,WACA,YACA,UACA,WACA,UACA,oBACA,mBACA,gBACe;AACf,UAAM,WAAW,KAAK,QAAQ,YAAY,KAAK,QAAQ;AACvD,UAAM,UAAU,KAAK,QAAQ,WAAW,KAAK,QAAQ;AAIrD,UAAM,gBAAgB,MAAM,SAAS,kBAAkB,KAAK,QAAQ,YAAY;AAChF,UAAM,eAAe,MAAM,QAAQ,kBAAkB,KAAK,QAAQ,WAAW;AAC7E,UAAM,eAAe,KAAK,yBAAyB,KAAK,QAAQ;AAChE,UAAM,cAAc,KAAK,wBAAwB,KAAK,QAAQ;AAC9D,UAAM,kBAAkB,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,YAAY;AACpF,UAAM,iBAAiB,aAAa,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,WAAW;AAIjF,UAAM,mBAAmB,KAAK,QAAQ,qBAAqB;AAC3D,UAAM,aAAa,mBACf,SACC,uBAAuB,iBAAiB,cAAc,YAAY,EAAE,SAAS,KAAK,IAAI,SAAS;AACpG,UAAM,YAAY,mBACd,SACC,sBAAsB,gBAAgB,cAAc,YAAY,EAAE,SAAS,KAAK,IAAI,SAAS;AAGlG,SAAK,OAAO;AAAA,MACV,4CAA4C,UAAU,oBAAoB,iBAAiB,gBAAgB,SAAS,WAAW,SAAS,oBAAoB,gBAAgB,gBAAgB,SAAS;AAAA,IACvM;AAEA,QAAI,KAAK,QAAQ,kBAAkB;AAEjC,WAAK,OAAO;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAQA,UAAM,iBAA2B;AAAA,MAC/B,GAAI,gBAAgB,QAAQ,CAAC,cAAc,OAAO,eAAe,KAAK,CAAC,IAAI,CAAC;AAAA,MAC5E;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,gBAA0B;AAAA,MAC9B,GAAI,gBAAgB,OAAO,CAAC,cAAc,OAAO,eAAe,IAAI,CAAC,IAAI,CAAC;AAAA,MAC1E;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MAAa;AAAA,MACb;AAAA,MAAW;AAAA,MACX;AAAA,MAAc;AAAA;AAAA,MACd;AAAA,MAAoB;AAAA;AAAA;AAAA,MAEpB,GAAG;AAAA;AAAA,MAEH,GAAG;AAAA;AAAA,MAEH;AAAA,MACA,cAAc,SAAS,IAAI,UAAU,qBAAqB,QAAQ,IAAI,SAAS,4BAA4B,SAAS,CAAC,IAAI,SAAS,CAAC;AAAA,MACnI;AAAA,MAAQ;AAAA,MACR;AAAA,MAAQ;AAAA;AAAA;AAAA;AAAA,MAGR;AAAA,MAAM;AAAA,MACN;AAAA,MAAe;AAAA,MACf;AAAA,MAAiB;AAAA,MACjB;AAAA,MAAgB;AAAA,MAChB;AAAA,MAAW;AAAA,MACX;AAAA,MAAS;AAAA,MACT;AAAA,MAAQ;AAAA,MACR;AAAA,MAAM;AAAA,MACN;AAAA;AAAA,IACF;AAEA,SAAK,OAAO;AAAA,MACV,sCAAsC,WAAW,KAAK,GAAG,CAAC;AAAA,IAC5D;AAIA,SAAK,gBAAgBA,OAAM,UAAU,YAAY;AAAA,MAC/C,OAAO,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IACxC,CAAC;AAGD,SAAK,cAAc,GAAG,SAAS,CAAC,UAAU;AACxC,WAAK,OAAO,QAAQ,mCAAmC,KAAK;AAC5D,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,SAAS;AACvC,UAAI,SAAS,KAAK,SAAS,MAAM;AAC/B,aAAK,OAAO,OAAO,6CAA6C,IAAI,EAAE;AAAA,MACxE;AACA,WAAK,KAAK,OAAO;AAAA,IACnB,CAAC;AAID,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,WAAK,mBAAmB,IAAI;AAAA,IAC9B,CAAC;AAGD,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,YAAM,SAAS,KAAK,SAAS;AAC7B,UAAI,OAAO,SAAS,OAAO,KAAK,OAAO,SAAS,OAAO,GAAG;AAExD,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,KAAK,0BAA0B,KAAM;AAC7C,eAAK,0BAA0B;AAC/B,eAAK,uBAAuB;AAAA,QAC9B;AACA,YAAI,KAAK,yBAAyB,GAAG;AACnC,eAAK,OAAO,QAAQ,oCAAoC,MAAM;AAAA,QAChE;AAAA,MACF;AAAA,IACF,CAAC;AAGD,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEQ,mBAAmB,MAAc;AACvC,QAAI,CAAC,MAAM,OAAQ;AAGnB,SAAK,eAAe,KAAK,aAAa,SAClC,OAAO,OAAO,CAAC,KAAK,cAAc,IAAI,GAAG,KAAK,aAAa,SAAS,KAAK,MAAM,IAC/E;AAGJ,UAAM,UAAU,IAAI,OAAO;AAC3B,QAAI,KAAK,aAAa,SAAS,SAAS;AAEtC,WAAK,OAAO,OAAO,0DAA0D,KAAK,aAAa,MAAM,oBAAoB;AACzH,WAAK,eAAe,KAAK,aAAa,SAAS,KAAK,aAAa,SAAS,OAAO,IAAI;AAAA,IACvF;AAKA,UAAM,cAAc,KAAK,8BAA8B;AACvD,QAAI,cAAc,GAAG;AACnB,WAAK,eAAe,KAAK,aAAa,SAAS,WAAW;AAAA,IAC5D;AAAA,EACF;AAAA,EAEQ,gCAAwC;AAC9C,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,KAAK,OAAQ,QAAO;AAEzB,UAAM,MAAM,IAAI;AAChB,QAAI,MAAM,EAAG,QAAO;AAEpB,UAAM,iBAAiB,CAAC,MAAsB;AAC5C,UAAI,IAAI,KAAK,OAAO,IAAI,CAAC,MAAM,KAAQ,IAAI,IAAI,CAAC,MAAM,GAAM;AAC1D,YAAI,IAAI,IAAI,CAAC,MAAM,EAAM,QAAO;AAChC,YAAI,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,MAAM,KAAQ,IAAI,IAAI,CAAC,MAAM,EAAM,QAAO;AAAA,MACzE;AACA,aAAO;AAAA,IACT;AAGA,QAAI,UAAU;AACd,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM,GAAG,EAAE,GAAG,KAAK;AAC9C,UAAI,eAAe,CAAC,GAAG;AACrB,kBAAU;AACV;AAAA,MACF;AAAA,IACF;AACA,QAAI,UAAU,EAAG,QAAO;AACxB,QAAI,UAAU,GAAG;AAEf,WAAK,eAAe,IAAI,SAAS,OAAO;AACxC,aAAO;AAAA,IACT;AAGA,UAAM,aAAuB,CAAC;AAC9B,aAAS,IAAI,GAAG,IAAI,MAAM,GAAG,KAAK;AAChC,UAAI,eAAe,CAAC,EAAG,YAAW,KAAK,CAAC;AAAA,IAC1C;AACA,QAAI,WAAW,SAAS,EAAG,QAAO;AAElC,UAAM,4BAA4B,CAAC,SAAyB;AAE1D,YAAM,MAAgB,CAAC;AACvB,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,cAAM,IAAI,KAAK,CAAC;AAChB,YAAI,KAAK,KAAK,KAAK,IAAI,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,KAAQ,MAAM,GAAM;AACxE;AAAA,QACF;AACA,YAAI,KAAK,CAAC;AAAA,MACZ;AACA,aAAO,OAAO,KAAK,GAAG;AAAA,IACxB;AAEA,UAAM,SAAS,CAAC,MAAc,cAAmE;AAC/F,YAAM,YAAY,KAAK,SAAS;AAChC,UAAI,IAAI;AAER,UAAI,QAAQ;AACZ,aAAO,IAAI,WAAW;AACpB,cAAM,OAAO,KAAK,KAAK,CAAC;AACxB,cAAM,MAAO,QAAS,KAAK,IAAI,KAAO;AACtC,YAAI,QAAQ,GAAG;AACb;AACA;AACA;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI,KAAK,UAAW;AAEpB;AACA,UAAI,UAAU,EAAG,QAAO,EAAE,OAAO,GAAG,MAAM,EAAE;AAC5C,UAAI,IAAI,QAAQ,UAAW;AAC3B,UAAI,OAAO;AACX,eAAS,IAAI,GAAG,IAAI,OAAO,KAAK,KAAK;AACnC,cAAM,OAAO,KAAK,KAAK,CAAC;AACxB,cAAM,MAAO,QAAS,KAAK,IAAI,KAAO;AACtC,eAAQ,QAAQ,IAAK;AAAA,MACvB;AACA,YAAM,SAAS,KAAK,SAAS,IAAI;AACjC,aAAO,EAAE,OAAO,MAAM,EAAE;AAAA,IAC1B;AAEA,UAAM,wBAAwB,CAAC,QAAyB;AACtD,UAAI,IAAI,SAAS,EAAG,QAAO;AAC3B,YAAM,UAAU,IAAI,CAAC,IAAK;AAC1B,UAAI,YAAY,KAAK,YAAY,EAAG,QAAO;AAE3C,YAAM,OAAO,0BAA0B,IAAI,SAAS,CAAC,CAAC;AACtD,YAAM,KAAK,OAAO,MAAM,CAAC;AACzB,UAAI,CAAC,GAAI,QAAO;AAChB,aAAO,GAAG,UAAU;AAAA,IACtB;AAEA,QAAI,iBAAiB;AACrB,QAAI,SAAS;AACb,QAAI,iBAAiB;AAGrB,aAAS,MAAM,GAAG,MAAM,WAAW,SAAS,GAAG,OAAO;AACpD,YAAM,QAAQ,WAAW,GAAG;AAC5B,YAAM,QAAQ,eAAe,KAAK;AAClC,UAAI,CAAC,MAAO;AACZ,YAAM,WAAW,QAAQ;AACzB,YAAM,SAAS,WAAW,MAAM,CAAC;AACjC,UAAI,YAAY,UAAU,YAAY,IAAK;AAC3C,YAAM,MAAM,IAAI,SAAS,UAAU,MAAM;AACzC,UAAI,CAAC,IAAI,OAAQ;AAEjB,YAAM,UAAU,IAAI,CAAC,IAAK;AAC1B,YAAM,QAAQ,YAAY;AAC1B,YAAM,QAAQ,YAAY,KAAK,YAAY;AAE3C,UAAI,OAAO;AAET,YAAI,UAAU,QAAQ,gBAAgB;AACpC,gBAAM,KAAK,IAAI,SAAS,gBAAgB,KAAK;AAC7C,cAAI,GAAG,OAAQ,MAAK,KAAK,cAAc,EAAE;AACzC,2BAAiB;AAAA,QACnB;AACA,yBAAiB;AACjB,iBAAS;AACT;AAAA,MACF;AAEA,UAAI,OAAO;AACT,cAAM,aAAa,sBAAsB,GAAG;AAC5C,YAAI,YAAY;AACd,cAAI,UAAU,QAAQ,gBAAgB;AACpC,kBAAM,KAAK,IAAI,SAAS,gBAAgB,KAAK;AAC7C,gBAAI,GAAG,OAAQ,MAAK,KAAK,cAAc,EAAE;AACzC,6BAAiB;AACjB,6BAAiB;AAAA,UACnB;AACA,mBAAS;AAAA,QACX,OAAO;AACL,mBAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAGA,WAAO,KAAK,IAAI,GAAG,cAAc;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,qBAAoC;AAChD,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,eAAe,CAAC,KAAK,YAAY;AAChE;AAAA,IACF;AAEA,UAAM,2BAA2B,KAAK,QAAQ,mBAAmB,QAAS,KAAK,QAAQ,4BAA4B;AAEnH,UAAM,aAAa,KAAK,cAAc,MAAM,CAAC;AAC7C,UAAM,YAAY,KAAK,cAAc,MAAM,CAAC;AAE5C,QAAI,CAAC,cAAc,CAAC,WAAW;AAC7B,WAAK,OAAO,QAAQ,8CAA8C;AAClE;AAAA,IACF;AAGA,UAAM,YAAY,YAAY;AAC5B,UAAI;AACF,YAAI,cAAc,CAAC;AACnB,YAAI,KAAK,iBAAiB,MAAM;AAC9B,cAAI;AACF,gBAAI,CAAC,4BAA4B,KAAK,gBAAgB,YAAY;AAChE,oBAAM,UAAU,WAAW,MAAM,KAAK,SAAS,KAAK,gBAAgB,IAAI,CAAC;AACzE,4BAAc,eAAe,QAAQ,KAAK,gBAAgB,UAAU;AACpE,kBAAI,CAAC,SAAS;AACZ,sBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,6BAAW,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,gBAC1C,CAAC;AAAA,cACH;AAAA,YACF;AACA,iBAAK,kBAAkB;AAAA,UACzB,QAAQ;AAAA,UAER;AAAA,QACF;AACA,yBAAiB,SAAS,KAAK,aAAc;AAC3C,cAAI,CAAC,KAAK,OAAQ;AAClB,cAAI,MAAM,OAAO;AAEf,gBAAI,CAAC,KAAK,YAAa,MAAK,cAAc;AAC1C,gBAAI,KAAK,gBAAgB,QAAS,MAAK,KAAK,cAAc,MAAM,IAAI;AACpE;AAAA,UACF;AAEA,cAAI,CAAC,aAAa;AAChB,gBAAI,CAAC,MAAM,WAAY;AACvB,0BAAc;AAAA,UAChB;AAEA,cAAI;AACF,kBAAM,UAAU,WAAW,MAAM,KAAK,SAAS,MAAM,IAAI,CAAC;AAC1D,gBAAI,CAAC,SAAS;AACZ,oBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,2BAAW,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,cAC1C,CAAC;AAAA,YACH;AAAA,UACF,SAAS,OAAO;AACd,kBAAM,OAAQ,OAAe;AAC7B,gBAAI,SAAS,WAAW,SAAS,8BAA8B;AAC7D,mBAAK,OAAO,MAAM,6CAA6C;AAC/D;AAAA,YACF;AACA,iBAAK,OAAO,QAAQ,gDAAgD,KAAK;AAAA,UAC3E;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,YAAI,KAAK,QAAQ;AACf,eAAK,OAAO,QAAQ,4CAA4C,KAAK;AAAA,QACvE;AAAA,MACF,UAAE;AACA,YAAI;AACF,qBAAW,IAAI;AAAA,QACjB,QAAQ;AAAA,QAAC;AAAA,MACX;AAAA,IACF;AAGA,UAAM,WAAW,YAAY;AAC3B,UAAI;AACF,YAAI,aAAa,CAAC;AAClB,YAAI,KAAK,gBAAgB,MAAM;AAC7B,cAAI;AACF,gBAAI,CAAC,4BAA4B,KAAK,eAAe,YAAY;AAC/D,oBAAM,UAAU,UAAU,MAAM,KAAK,SAAS,KAAK,eAAe,IAAI,CAAC;AACvE,2BAAa,cAAc,QAAQ,KAAK,eAAe,UAAU;AACjE,kBAAI,CAAC,SAAS;AACZ,sBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,4BAAU,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,gBACzC,CAAC;AAAA,cACH;AAAA,YACF;AACA,iBAAK,iBAAiB;AAAA,UACxB,QAAQ;AAAA,UAER;AAAA,QACF;AACA,yBAAiB,SAAS,KAAK,YAAa;AAC1C,cAAI,CAAC,KAAK,OAAQ;AAClB,cAAI,MAAM,OAAO;AAEf,gBAAI,CAAC,KAAK,YAAa,MAAK,cAAc;AAC1C,gBAAI,KAAK,gBAAgB,OAAQ,MAAK,KAAK,cAAc,MAAM,IAAI;AACnE;AAAA,UACF;AAEA,cAAI,CAAC,YAAY;AACf,gBAAI,CAAC,MAAM,WAAY;AACvB,yBAAa;AAAA,UACf;AAEA,cAAI;AACF,kBAAM,UAAU,UAAU,MAAM,KAAK,SAAS,MAAM,IAAI,CAAC;AACzD,gBAAI,CAAC,SAAS;AACZ,oBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,0BAAU,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,cACzC,CAAC;AAAA,YACH;AAAA,UACF,SAAS,OAAO;AACd,kBAAM,OAAQ,OAAe;AAC7B,gBAAI,SAAS,WAAW,SAAS,8BAA8B;AAC7D,mBAAK,OAAO,MAAM,4CAA4C;AAC9D;AAAA,YACF;AACA,iBAAK,OAAO,QAAQ,+CAA+C,KAAK;AAAA,UAC1E;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,YAAI,KAAK,QAAQ;AACf,eAAK,OAAO,QAAQ,2CAA2C,KAAK;AAAA,QACtE;AAAA,MACF,UAAE;AACA,YAAI;AACF,oBAAU,IAAI;AAAA,QAChB,QAAQ;AAAA,QAAC;AAAA,MACX;AAAA,IACF;AAGA,YAAQ,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU;AACtD,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,QAAQ,gDAAgD,KAAK;AACzE,aAAK,KAAK,SAAS,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,SAAS,YAA4B;AAC3C,QAAI,CAAC,YAAY,OAAQ,QAAO;AAGhC,QAAI,WAAW,UAAU,KAAK,WAAW,CAAC,MAAM,KAAQ,WAAW,CAAC,MAAM,GAAM;AAC9E,UAAI,WAAW,CAAC,MAAM,EAAM,QAAO;AACnC,UAAI,WAAW,UAAU,KAAK,WAAW,CAAC,MAAM,KAAQ,WAAW,CAAC,MAAM,EAAM,QAAO;AAAA,IACzF;AAIA,QAAI;AACF,UAAI,MAAM;AACV,YAAM,QAAkB,CAAC;AACzB,aAAO,MAAM,KAAK,WAAW,QAAQ;AACnC,cAAM,IAAI,WAAW,aAAa,GAAG;AACrC,eAAO;AACP,YAAI,CAAC,KAAK,MAAM,IAAI,WAAW,QAAQ;AACrC,iBAAO;AAAA,QACT;AACA,cAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,cAAM,KAAK,WAAW,SAAS,KAAK,MAAM,CAAC,CAAC;AAC5C,eAAO;AAAA,MACT;AACA,UAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,aAAO,OAAO,OAAO,KAAK;AAAA,IAC5B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AAEA,SAAK,SAAS;AACd,SAAK,OAAO,MAAM,gDAAgD;AAGlE,QAAI,KAAK,eAAe;AACtB,UAAI;AACF,aAAK,cAAc,OAAO,IAAI;AAC9B,aAAK,cAAc,KAAK,SAAS;AACjC,mBAAW,MAAM;AACf,cAAI;AACF,iBAAK,eAAe,KAAK,SAAS;AAAA,UACpC,QAAQ;AAAA,UAAC;AAAA,QACX,GAAG,GAAI;AAAA,MACT,QAAQ;AAAA,MAAC;AACT,WAAK,gBAAgB;AAAA,IACvB;AAEA,QAAI,KAAK,cAAc,UAAU;AAG/B,YAAM,QAAQ,IAAI;AAAA,QAChB,KAAK,eAAe,KAAK,WAAW;AAAA,QACpC,KAAK,eAAe,KAAK,UAAU;AAAA,MACrC,CAAC;AACD,WAAK,cAAc;AACnB,WAAK,aAAa;AAAA,IACpB,OAAO;AACL,WAAK,cAAc;AACnB,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,OAAO,MAAM,4CAA4C;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AACF;;;AD5gCA,eAAsB,uBACpB,SAC2B;AAC3B,QAAM,kBACJ,OAAO,YAAY,eAAe,OAAO,QAAQ,QAAQ,WACrD,QAAQ,MACR;AACN,QAAM,oBAAoB,MAAM;AAC9B,QAAI;AACF,YAAM,IAAI;AACV,YAAM,IAAI;AACV,UAAI,CAAC,EAAE,CAAC,GAAG;AACT,UAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAAA,MAC3C;AACA,aAAO,OAAO,EAAE,CAAC,CAAC;AAAA,IACpB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,QAAM,YAAY,CAAC,YAAoB;AACrC,QAAI;AACF,YAAM,SAAc,QAAQ;AAC5B,YAAM,SAAS,8BAA8B,eAAe,SAAS,gBAAgB;AAErF,UAAI,QAAQ,KAAM,QAAO,KAAK,GAAG,MAAM,IAAI,OAAO,EAAE;AAAA,eAC3C,QAAQ,IAAK,QAAO,IAAI,GAAG,MAAM,IAAI,OAAO,EAAE;AAAA,eAC9C,QAAQ,MAAO,QAAO,MAAM,GAAG,MAAM,IAAI,OAAO,EAAE;AAAA,IAC7D,QAAQ;AAAA,IAER;AAAA,EACF;AAKA,QAAM,mCAAmC,CAAC,OAAmC;AAC3E,QAAI,OAAO,OAAO,YAAY,OAAO,SAAS,EAAE,KAAK,KAAK,EAAG,QAAO;AACpE,WAAO;AAAA,EACT;AASA,QAAM,YAAY,sBAAsB,OAAO;AAC/C,MAAI,CAAC,WAAW;AACd;AAAA,MACE;AAAA,IACF;AACA,WAAO,MAAM,+BAA+B,OAAO;AAAA,EACrD;AAEA;AAAA,IACE,eAAe,sBAAsB,SAAS,CAAC,iBAAiB,QAAQ,eAAe,KAAK;AAAA,EAC9F;AAEA,QAAM,WAAW,qBAAqB,IAAI,SAAS;AACnD,MAAI,YAAY,SAAS,OAAO,OAAO,WAAW;AAChD;AAAA,MACE,uBAAuB,SAAS,OAAO,IAAI,SAAS,SAAS,OAAO,IAAI,aAAa,SAAS,QAAQ;AAAA,IACxG;AACA,WAAO,2BAA2B,WAAW,UAAU,SAAS;AAAA,EAClE;AACA,MAAI,UAAU;AAEZ;AAAA,MACE,0CAA0C,SAAS,OAAO,OAAO,SAAS;AAAA,IAC5E;AACA,yBAAqB,OAAO,SAAS;AAAA,EACvC;AAEA,QAAM,WAAW,qBAAqB,IAAI,SAAS;AACnD,MAAI,UAAU;AACZ,cAAU,2BAA2B;AACrC,UAAM;AACN,UAAMC,WAAU,qBAAqB,IAAI,SAAS;AAClD,QAAIA,UAAS;AACX;AAAA,QACE,yCAAyCA,SAAQ,OAAO,IAAI,SAASA,SAAQ,OAAO,IAAI,aAAaA,SAAQ,QAAQ;AAAA,MACvH;AACA,aAAO,2BAA2B,WAAWA,UAAS,SAAS;AAAA,IACjE;AAEA;AAAA,MACE;AAAA,IACF;AACA,WAAO,MAAM,+BAA+B,OAAO;AAAA,EACrD;AAEA,QAAM,iBAAiB,YAAY;AACjC,UAAM,iBAAiB;AAAA,MACrB,QAAQ;AAAA,IACV;AACA;AAAA,MACE,sDAAsD,cAAc;AAAA,IACtE;AACA,UAAM,SAAS,MAAM,+BAA+B;AAAA,MAClD,GAAG;AAAA;AAAA,MAEH;AAAA,IACF,CAAC;AAED,UAAM,QAA4B;AAAA,MAChC;AAAA,MACA,UAAU;AAAA,IACZ;AACA,yBAAqB,IAAI,WAAW,KAAK;AACzC,cAAU,gBAAgB,OAAO,IAAI,SAAS,OAAO,IAAI,EAAE;AAG3D,WAAO,OAAO,KAAK,SAAS,MAAM;AAChC,YAAM,UAAU,qBAAqB,IAAI,SAAS;AAClD,UAAI,SAAS,WAAW,QAAQ;AAC9B,kBAAU,uDAAuD;AACjE,6BAAqB,OAAO,SAAS;AAAA,MACvC;AAAA,IACF,CAAC;AAAA,EACH,GAAG,EAAE,QAAQ,MAAM;AACjB,yBAAqB,OAAO,SAAS;AAAA,EACvC,CAAC;AAED,uBAAqB,IAAI,WAAW,aAAa;AACjD,QAAM;AAEN,QAAM,UAAU,qBAAqB,IAAI,SAAS;AAClD,MAAI,QAAS,QAAO,2BAA2B,WAAW,SAAS,SAAS;AAC5E,SAAO,MAAM,+BAA+B,OAAO;AACrD;AAOA,IAAM,uBAAuB,oBAAI,IAAgC;AACjE,IAAM,uBAAuB,oBAAI,IAA2B;AAE5D,IAAM,wBAAwB,CAC5B,YACuB;AAEvB,MAAI,CAAC,QAAQ,SAAU;AAIvB,QAAM,aACJ,QAAQ,YAAY,SAAY,cAAc,OAAO,QAAQ,OAAO;AACtE,QAAM,aAAa,QAAQ,WAAW;AAMtC,QAAM,cAAc,QAAQ,QAAQ,WAAW;AAG/C,QAAM,mBAAmB,QAAQ,oBAAoB;AACrD,QAAM,mBAAmB,QAAQ,oBAAoB;AAErD,SAAO;AAAA,IACL;AAAA,IACA,UAAU,QAAQ,QAAQ;AAAA,IAC1B,MAAM,UAAU;AAAA,IAChB,WAAW,QAAQ,OAAO;AAAA,IAC1B,WAAW,UAAU;AAAA,IACrB,QAAQ,cAAc,MAAM,GAAG;AAAA,IAC/B,OAAO,gBAAgB;AAAA,IACvB,OAAO,gBAAgB;AAAA,EACzB,EAAE,KAAK,GAAG;AACZ;AAEA,IAAM,wBAAwB,CAAC,QAAwB;AAGrD,SAAO,IAAI,QAAQ,iBAAiB,kBAAkB;AACxD;AAEA,IAAM,6BAA6B,CACjC,KACA,OACA,cACqB;AACrB,QAAM;AACN,cAAY,sBAAsB,GAAG,aAAa,MAAM,QAAQ,EAAE;AAElE,MAAI,WAAW;AACf,QAAM,YAAY,MAAM;AAExB,QAAM,QAAQ,OAAO,WAAoC;AACvD,QAAI,SAAU;AACd,eAAW;AAEX,UAAM,UAAU,qBAAqB,IAAI,GAAG;AAE5C,QAAI,CAAC,WAAW,QAAQ,WAAW,UAAW;AAE9C,YAAQ,WAAW,KAAK,IAAI,GAAG,QAAQ,WAAW,CAAC;AACnD;AAAA,MACE,sBAAsB,GAAG,aAAa,QAAQ,QAAQ,WAAW;AAAA,QAC9D,QAAgB,WAAW,UAAU;AAAA,MACxC,CAAC;AAAA,IACH;AACA,QAAI,QAAQ,WAAW,EAAG;AAE1B,yBAAqB,OAAO,GAAG;AAC/B,gBAAY,+CAA+C;AAC3D,UAAM,UAAU,MAAM,UAAU,wBAAwB;AAAA,EAC1D;AAGA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,EACF;AACF;AAEA,eAAe,+BACb,SAC2B;AAC3B,QAAM,cAAc,QAAQ,YAAY;AAExC,QAAM,gCAAgC,CACpCC,iBAOe;AACf,QAAI,CAACA,aAAa;AAClB,UAAM,KAAK,OAAOA,YAAW;AAE7B,UAAM,YAAY,CAAC,MACjB,MAAM,UAAU,MAAM,SAAS,MAAM,QAChC,IACD;AAON,QACE,GAAG,WAAW,mBAAmB,KACjC,GAAG,WAAW,iBAAiB,GAC/B;AACA,YAAM,SAA4B,GAAG,WAAW,iBAAiB,IAC7D,SACA;AACJ,YAAM,QAAQ,GAAG,MAAM,GAAG,EAAE,OAAO,OAAO;AAG1C,UAAI,MAAM,UAAU,GAAG;AACrB,cAAM,QAAQ,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC;AACpD,cAAM,OAAO,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC;AACnD,cAAM,eAAe,UAAU,KAAK;AACpC,cAAM,cAAc,UAAU,IAAI;AAClC,eAAO;AAAA,UACL;AAAA,UACA,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,UACvC,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,QACvC;AAAA,MACF;AACA,aAAO,EAAE,OAAO;AAAA,IAClB;AAEA;AAAA,EACF;AAEA,QAAM,gBAA0C;AAAA,IAC9C,SAAS,QAAQ;AAAA,IACjB,WAAW;AAAA,IACX,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,IACpE,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,EACtE;AAEA,QAAM,UAAU,QAAQ,OAAQ,MAAM,QAAQ,SAAS,aAAa;AACpE,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AAEA,QAAM,wBACJ,QAAQ,iBAAkB,MAAM,QAAQ,mBAAmB;AAE7D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,qBAAqB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,MAAI;AAOJ,QAAM,cAAc,oBAAI,IAAwB;AAChD,cAAY,IAAI,OAAO;AACvB,MAAI,uBAAuB;AACzB,gBAAY,IAAI,sBAAsB,QAAQ;AAChD,MAAI,uBAAuB;AACzB,gBAAY,IAAI,sBAAsB,OAAO;AAI/C,QAAM,kBAAkB,uBAAuB,cAAc,MAAS;AACtE,QAAM,gBACJ,WAAW,YAAY,YAAY,YAAY,OAAO,KAAK;AAC7D,QAAM,YAAY,cACd,qCAAqC,OAAO,GAAG,aAAa,GAAG,cAAc,OAAO,WAAW,KAAK,EAAE,MACtG,sBAAsB,OAAO,YAAY,OAAO,GAAG,aAAa;AACpE,QAAM,MAAM,CAAC,YAAoB;AAC/B,QAAI;AACF,UAAI,QAAQ,MAAM;AAChB,eAAO,KAAK,GAAG,SAAS,IAAI,OAAO,EAAE;AAAA,MACvC,WAAW,QAAQ,KAAK;AACtB,eAAO,IAAI,GAAG,SAAS,IAAI,OAAO,EAAE;AAAA,MACtC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,qBAAqB,CAAC,WAAmB;AAC7C,UAAM,UAAU,QAAQ,4BAA4B;AACpD;AAAA,MACE,GAAG,MAAM,eAAe,QAAQ,KAAK,UAAU,QAAQ,QAAQ,IAAI,KAAK,QAAQ,KAAK,KAAK,IAAI,CAAC,MAAM,EAAE;AAAA,IACzG;AAAA,EACF;AAEA;AAAA,IACE,kBAAkB,IAAI,YAAY,gBAAgB,YAAY,gBAAgB,sBAAsB,qBAAqB,KAAK,sBAAsB,iBAAiB,oBAAoB,eAAe,mBAAmB,cAAc,cAAc,WAAW;AAAA,EACpQ;AAEA,MAAI;AACJ,MAAI,oBAAoB;AAExB,MAAI,aAAa;AAEf,UAAM,eAAe,kBAAkB,gBAAgB;AACvD,UAAM,cAAc,kBAAkB,eAAe;AACrD,UAAM,YAAY,8BAA8B,WAAW;AAG3D,UAAM,gBAA+B;AACrC,QAAI,mBACF,WAAW,eAAe;AAM5B,QAAI,kBAAkB;AACtB,QAAI;AACF,YAAM,mBAAmB,uBAAuB,YAAY;AAC5D,YAAM,WACJ,MAAM,iBAAiB,kBAAkB,YAAY;AACvD,YAAM,UAAiB,MAAM,QAAQ,QAAQ,IACzC,WACA,MAAM,QAAQ,UAAU,OAAO,IAC7B,SAAS,UACT,CAAC;AACP,YAAM,OAAO,QAAQ,KAAK,CAAC,MAAW,GAAG,YAAY,MAAM;AAC3D,YAAM,MACJ,OAAO,MAAM,iBAAiB,WAC1B,KAAK,aAAa,YAAY,IAC9B;AACN,wBAAkB,IAAI,SAAS,KAAK;AAAA,IACtC,QAAQ;AAAA,IAER;AAEA,QAAI,oBACF,WAAW,iBACV,kBAAkB,QACf,QACA,kBAAkB,UAAU,kBAC1B,SACA;AAER,QAAI,WAAW,eAAe,UAAU,gBAAgB,eAAe;AACrE;AAAA,QACE,uDAAuD,UAAU,WAAW,WAAW,aAAa;AAAA,MACtG;AAAA,IACF;AACA;AAAA,MACE,4CAA4C,aAAa,aAC3C,YAAY,aAAa,iBAAiB,cAAc,WAAW,aAAa,gBAAgB,YAClG,WAAW,UAAU,QAAQ,oBAAoB,eAAe;AAAA,IAC9E;AAEA,UAAM,WAAW,uBAAuB,YAAY;AACpD,UAAM,UAAU,uBAAuB,WAAW;AAIlD,UAAM,YAAY,kBAAkB;AACpC,UAAM,mBAAmB,kBAAkB;AAG3C,QAAI;AACJ,QAAI;AACJ,QAAI,WAAW,WAAW,QAAQ;AAChC,YAAM,QAAQ,QAAQ,kBAAkB,KAAK;AAE7C,YAAM,qBAAqB,OAAO,WAKoC;AACpE,cAAM,EAAE,YAAY,IAAI,MAAM,QAAQ,wBAAwB;AAAA,UAC5D,SAAS,OAAO;AAAA,UAChB;AAAA,UACA,eAAe;AAAA,UACf,MAAM,OAAO;AAAA,QACf,CAAC;AAED,cAAM,aAAa,YAAY;AAAA,UAC7B,CAAC,MACC,EAAE,cAAc,UAChB,EAAE,SAAS,OAAO,eAClB,QAAQ,EAAE,WAAW;AAAA,QACzB;AAEA,cAAM,QAAQ,WAAW,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,OAAO;AACjE,YAAI,OAAO,aAAa;AACtB,iBAAO;AAAA,YACL,aAAa,MAAM;AAAA,YACnB,eAAe,MAAM;AAAA,UACvB;AAAA,QACF;AAIA,YAAI,OAAO,YAAY,OAAO;AAC5B,gBAAM,eAAe,WAAW,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM;AAChE,cAAI,cAAc,aAAa;AAC7B,mBAAO;AAAA,cACL,aAAa,aAAa;AAAA,cAC1B,eAAe,aAAa;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AAEA,cAAM,YAAY,YACf;AAAA,UACC,CAAC,MAAM,EAAE,cAAc,UAAU,EAAE,SAAS,OAAO;AAAA,QACrD,EACC,IAAI,CAAC,MAAM,GAAG,EAAE,OAAO,IAAI,EAAE,EAAE,EAAE,EACjC,KAAK,IAAI;AAEZ,cAAM,IAAI;AAAA,UACR,gDAAgD,OAAO,WAAW,6BAA6B,OAAO,OAAO,YAAY,OAAO,OAAO,WAAW,KAAK,kBACtI,aAAa,MAAM;AAAA,QAEtC;AAAA,MACF;AAGA,YAAM,gBAAgB,MAAM,mBAAmB;AAAA,QAC7C,SAAS;AAAA,QACT,MAAM;AAAA,QACN,aAAa;AAAA,QACb,SAAS;AAAA,MACX,CAAC;AAED,qBAAe,cAAc;AAC7B,0BAAoB,cAAc;AAGlC,YAAM,eAAe,MAAM,mBAAmB;AAAA,QAC5C,SAAS;AAAA,QACT,MAAM;AAAA,QACN,aAAa;AAAA,QACb,SAAS;AAAA,MACX,CAAC;AAED,oBAAc,aAAa;AAC3B,UAAI,aAAa,kBAAkB,kBAAkB;AACnD;AAAA,UACE,iDAAiD,gBAAgB,WAAW,aAAa,aAAa;AAAA,QACxG;AAAA,MACF;AACA,yBAAmB,aAAa;AAAA,IAClC;AAEA,kBAAc,IAAI,gBAAgB;AAAA,MAChC,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd,aAAa;AAAA,MACb,GAAI,gBAAgB,cAAc,EAAE,cAAc,YAAY,IAAI,CAAC;AAAA,MACnE,GAAI,kBAAkB,gBAClB,EAAE,eAAe,iBAAiB,cAAc,IAChD,CAAC;AAAA,MACL,aAAa,kBAAkB,eAAe;AAAA,MAC9C,SAAS,kBAAkB,WAAW;AAAA;AAAA,MAEtC,WAAW,kBAAkB,aAAa;AAAA,MAC1C,GAAI,kBAAkB,UAAU,SAC5B,EAAE,OAAO,iBAAiB,MAAM,IAChC,CAAC;AAAA,MACL,GAAI,cAAc,SACd,EAAE,UAAU,IACZ,mBACE,EAAE,WAAW,KAAK,IAClB,CAAC;AAAA,MACP,GAAI,kBAAkB,qBAAqB,SACvC,EAAE,kBAAkB,iBAAiB,iBAAiB,IACtD,CAAC;AAAA,MACL,GAAI,kBAAkB,qBAAqB,SACvC,EAAE,kBAAkB,iBAAiB,iBAAiB,IACtD,CAAC;AAAA,MACL;AAAA,IACF,CAAC;AAED,wBAAoB;AACpB,UAAM,YAAY,MAAM;AACxB;AAAA,MACE;AAAA,IACF;AAAA,EACF,OAAO;AAEL,UAAM,KAAK;AAMX,UAAM,WAAW,QAAQ;AACzB,QAAI;AAEJ,QAAI,UAAU;AACZ,YAAM,aAAa,QAAQ,QAAQ,MAAM,EAAE,IAAI,OAAO,GAAG,WAAW,YAAY,YAAY,IAAI,OAAO,KAAK,EAAE;AAC9G,yBAAmB,MAAM,QAAQ;AAAA,QAC/B;AAAA,QACA;AAAA,MACF;AACA,qBAAe,iBAAiB;AAChC,yBAAmB,8BAA8B,UAAU,EAAE;AAAA,IAC/D,OAAO;AACL,qBAAe,QAAQ;AAAA,IACzB;AAEA,kBAAc,IAAI,oBAAoB;AAAA,MACpC,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,YAAY,MAAM;AACxB;AAAA,MACE,sBAAsB,EAAE,YAAY,OAAO,GAAG,WAAW,cAAc,QAAQ,KAAK,EAAE;AAAA,IACxF;AAAA,EACF;AAEA,QAAM,kBAAkB,YAMnB;AACH,QAAI,mBAAmB;AAErB,aAAO,MAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC5C,cAAM,UAAU,WAAW,MAAM;AAC/B,kBAAQ;AACR;AAAA,YACE,IAAI;AAAA,cACF,4DAA4D,OAAO;AAAA,YACrE;AAAA,UACF;AAAA,QACF,GAAG,iBAAiB;AAEpB,cAAM,UAAU,CAAC,MAAe;AAC9B,kBAAQ;AACR,iBAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,QACtD;AAEA,cAAM,UAAU,CAAC,UAAkB;AAGjC,gBAAM,YAAuB;AAE7B,cAAI;AACF,kBAAM,EAAE,KAAK,KAAK,eAAe,IAC/B,mCAAmC,KAAK;AAC1C,gBAAI,CAAC,OAAO,CAAC,KAAK;AAEhB;AAAA,YACF;AACA,oBAAQ;AACR,oBAAQ;AAAA,cACN;AAAA,cACA,YAAY;AAAA,cACZ,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;AAAA,cAC3C,MAAM,EAAE,KAAK,IAAI;AAAA,YACnB,CAAC;AAAA,UACH,SAAS,GAAG;AAEV;AAAA,UACF;AAAA,QACF;AAEA,cAAM,UAAU,MAAM;AACpB,kBAAQ;AACR;AAAA,YACE,IAAI;AAAA,cACF,oDAAoD,OAAO;AAAA,YAC7D;AAAA,UACF;AAAA,QACF;AAEA,cAAM,UAAU,MAAM;AACpB,uBAAa,OAAO;AACpB,UAAC,YAAgC;AAAA,YAC/B;AAAA,YACA;AAAA,UACF;AACA,UAAC,YAAgC;AAAA,YAC/B;AAAA,YACA;AAAA,UACF;AACA,UAAC,YAAgC;AAAA,YAC/B;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAEA,QAAC,YAAgC,GAAG,SAAgB,OAAc;AAClE,QAAC,YAAgC;AAAA,UAC/B;AAAA,UACA;AAAA,QACF;AACA,QAAC,YAAgC,GAAG,SAAgB,OAAc;AAAA,MACpE,CAAC;AAAA,IACH,OAAO;AAEL,aAAO,MAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC5C,cAAM,UAAU,WAAW,MAAM;AAC/B,kBAAQ;AACR;AAAA,YACE,IAAI;AAAA,cACF,yDAAyD,OAAO,YAAY,OAAO;AAAA,YACrF;AAAA,UACF;AAAA,QACF,GAAG,iBAAiB;AAEpB,cAAM,UAAU,CAAC,MAAe;AAC9B,kBAAQ;AACR,iBAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,QACtD;AAEA,cAAM,OAAO,CAAC,OAAY;AACxB,cAAI,CAAC,IAAI,WAAY;AACrB,gBAAM,YAAY,GAAG;AACrB,gBAAM,aAAa,GAAG;AAEtB,cAAI,cAAc,QAAQ;AACxB,kBAAM,EAAE,KAAAC,MAAK,KAAAC,MAAK,eAAe,IAC/B,mCAAmC,UAAU;AAC/C,gBAAI,CAACD,QAAO,CAACC,KAAK;AAClB,oBAAQ;AACR,oBAAQ;AAAA,cACN;AAAA,cACA;AAAA,cACA,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;AAAA,cAC3C,MAAM,EAAE,KAAAD,MAAK,KAAAC,KAAI;AAAA,YACnB,CAAC;AACD;AAAA,UACF;AAEA,gBAAM,EAAE,KAAK,KAAK,IAAI,IACpB,mCAAmC,UAAU;AAC/C,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAK;AAC1B,kBAAQ;AACR,kBAAQ,EAAE,WAAW,YAAY,MAAM,EAAE,KAAK,KAAK,IAAI,EAAE,CAAC;AAAA,QAC5D;AAEA,cAAM,UAAU,MAAM;AACpB,uBAAa,OAAO;AACpB,UAAC,YAAoC;AAAA,YACnC;AAAA,YACA;AAAA,UACF;AACA,UAAC,YAAoC;AAAA,YACnC;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAEA,QAAC,YAAoC,GAAG,SAAgB,OAAc;AACtE,QAAC,YAAoC;AAAA,UACnC;AAAA,UACA;AAAA,QACF;AAMA,cAAM,aACJ,YACA,6BAA6B;AAC/B,YAAI,YAAY;AACd,kBAAQ;AACR,iBAAO,UAAU;AAAA,QACnB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,gBAAgB;AAAA,EACnC,SAAS,GAAG;AAGV,QAAI;AACF,YAAM,YAAY,KAAK;AAAA,IACzB,QAAQ;AAAA,IAER;AAGA,QAAI,kBAAkB;AACpB,UAAI;AACF,cAAM,iBAAiB,QAAQ;AAAA,MACjC,QAAQ;AAAA,MAER;AAAA,IACF;AAMA,QAAI,CAAC,kBAAkB;AACrB,UAAI,oBAAoB;AACtB,cAAM,QAAQ;AAAA,UACZ,MAAM,KAAK,WAAW,EAAE,IAAI,OAAO,MAAM;AACvC,gBAAI;AACF,oBAAM,EAAE,MAAM;AAAA,YAChB,QAAQ;AAAA,YAER;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AAIL,cAAM,UAAU,cAAc,MAAQ;AACtC,mBAAW,KAAK,MAAM,KAAK,WAAW,GAAG;AACvC,cAAI;AACF,YAAC,GAAW,QAAQ;AAAA,cAClB;AAAA,cACA;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACA,MAAI,qBAAqB,SAAS,cAAc,mBAAmB;AACjE;AAAA,MACE,wCAAwC,iBAAiB,WAAW,SAAS,SAAS;AAAA,IACxF;AAAA,EACF;AAGA,MAAI,MAAM;AACV,MAAI;AACF,QAAI,aAAa;AAEf,YAAM,eAAe,kBAAkB,gBAAgB;AACvD,YAAM,WAAW,uBAAuB,YAAY;AACpD,YAAM,WAAgB,MAAM,SAAS,kBAAkB,YAAY;AACnE,YAAM,UAAiB,MAAM,QAAQ,QAAQ,IACzC,WACA,MAAM,QAAQ,UAAU,OAAO,IAC7B,SAAS,UACT,CAAC;AAEP,YAAM,YAAY,8BAA8B,WAAW;AAC3D,YAAM,uBACJ,WAAW,eAAe;AAC5B,UAAI,kBAAkB;AACtB,UAAI;AACF,cAAM,OAAO,QAAQ,KAAK,CAAC,MAAW,GAAG,YAAY,MAAM;AAC3D,cAAM,MACJ,OAAO,MAAM,iBAAiB,WAC1B,KAAK,aAAa,YAAY,IAC9B;AACN,0BAAkB,IAAI,SAAS,KAAK;AAAA,MACtC,QAAQ;AAAA,MAER;AACA,YAAM,eACJ,WAAW,iBACV,yBAAyB,QACtB,QACA,yBAAyB,UAAU,kBACjC,SACA;AACR,YAAM,SAAS,QAAQ,KAAK,CAAC,MAAW,GAAG,YAAY,YAAY;AACnE,YAAM,KAAK,OAAO,QAAQ,SAAS;AACnC,UAAI,OAAO,SAAS,EAAE,KAAK,KAAK,EAAG,OAAM;AAAA,IAC3C,OAAO;AACL,YAAM,WAAgB,MAAM,QAAQ,kBAAkB,OAAQ;AAC9D,YAAM,UAAiB,MAAM,QAAQ,QAAQ,IACzC,WACA,MAAM,QAAQ,UAAU,OAAO,IAC7B,SAAS,UACT,CAAC;AACP,YAAM,SAAS,QAAQ,KAAK,CAAC,MAAW,GAAG,YAAY,OAAO;AAC9D,YAAM,KAAK,OAAO,QAAQ,SAAS;AACnC,UAAI,OAAO,SAAS,EAAE,KAAK,KAAK,EAAG,OAAM;AAAA,IAC3C;AAAA,EACF,QAAQ;AAAA,EAER;AAIA,MAAI;AAQJ,QAAM,gBAAgB,YAAmC;AACvD,WAAO,MAAM,IAAI,QAAQ,CAAC,YAAY;AACpC,UAAI,cAAc;AAClB,UAAI,gBAAgB;AACpB,YAAM,sBAAsB,oBACxB,KAAK,IAAI,KAAQ,iBAAiB,IAClC;AACJ,YAAM,UAAU,WAAW,MAAM;AAC/B,gBAAQ;AACR,YAAI,CAAC,aAAa;AAChB,kBAAQ,MAAS;AACjB;AAAA,QACF;AAEA,cAAM,OAAO,gBAAgB,EAAE,YAAY,KAAM,UAAU,EAAE;AAC7D,cAAM,YAAY,+BAA+B;AAAA,UAC/C,YAAY,KAAK;AAAA,UACjB,UAAU,KAAK;AAAA,QACjB,CAAC;AACD,YAAI,CAAC,WAAW;AACd,iBAAO;AAAA,YACL,6EAA6E,KAAK,UAAU,aAAa,KAAK,QAAQ;AAAA,UACxH;AACA,kBAAQ,MAAS;AACjB;AAAA,QACF;AAEA,eAAO;AAAA,UACL,6FAA6F,KAAK,UAAU,aAAa,KAAK,QAAQ;AAAA,QACxI;AACA,gBAAQ;AAAA,UACN,YAAY,KAAK;AAAA,UACjB,UAAU,KAAK;AAAA,UACf;AAAA,UACA,MAAM;AAAA,QACR,CAAC;AAAA,MACH,GAAG,mBAAmB;AAEtB,YAAM,UAAU,CAAC,UAAkB;AACjC,sBAAc;AACd,cAAM,SAAS,gBAAgB,KAAK;AACpC,YAAI,CAAC,QAAQ;AACX,cAAI,kBAAkB,GAAG;AACvB,kBAAM,OAAO,MACV,SAAS,GAAG,KAAK,IAAI,IAAI,MAAM,MAAM,CAAC,EACtC,SAAS,KAAK;AACjB,mBAAO;AAAA,cACL,mCAAmC,MAAM,MAAM,SAAS,IAAI;AAAA,YAC9D;AAAA,UACF;AACA;AAAA,QACF;AACA,gBAAQ;AACR,gBAAQ;AAAA,UACN,YAAY,OAAO;AAAA,UACnB,UAAU,OAAO;AAAA,UACjB,WAAW,OAAO;AAAA,UAClB,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAEA,YAAM,UAAU,MAAM;AACpB,qBAAa,OAAO;AACpB,QAAC,aAAqB;AAAA,UACpB;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,MAAC,aAAqB,KAAK,cAAqB,OAAc;AAAA,IAChE,CAAC;AAAA,EACH;AAEA,UAAQ,MAAM,cAAc;AAE5B,QAAM,QAAwB;AAAA,IAC5B,WAAW,SAAS;AAAA,IACpB,aAAa;AAAA,IACb,GAAI,SAAS,cAAc,SACvB;AAAA,MACE,MAAM;AAAA,QACJ,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,QACpB,GAAI,SAAS,iBACT,EAAE,gBAAgB,SAAS,eAAe,IAC1C,CAAC;AAAA,MACP;AAAA,IACF,IACA;AAAA,MACE,MAAM;AAAA,QACJ,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,MACtB;AAAA,IACF;AAAA,EACN;AAEA,QAAM,WAAoC,QACtC;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,MAAM;AAAA,IAClB,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,EACnB,IACA;AAEJ,QAAM,MAAM,gBAAgB,OAAO,QAAQ;AAC3C,QAAM,YAAY,MAChB,IAAI;AAAA,IACF;AAAA,IACA;AAAA,IACA,WAAW,mBAAmB;AAAA,IAC9B;AAAA,EACF;AACF,MAAI,QAAQ,UAAU;AAEtB;AAAA,IACE,gBAAgB,SAAS,SAAS,QAAQ,GAAG,GAAG,WAAW,cAAc,SAAS,UAAU,IAAI,SAAS,QAAQ,KAAK,aAAa;AAAA,EACrI;AAEA,MAAI,aAAa;AACjB,QAAM,UAAU,oBAAI,IAAgB;AACpC,MAAI;AACJ,MAAI,cAAc;AAClB,MAAI,aAAa;AAGjB,MAAI,iBAAiB,KAAK,IAAI;AAC9B,QAAM,gBAAgB,MAAM;AAC1B,qBAAiB,KAAK,IAAI;AAAA,EAC5B;AAEA,QAAM,qBAAqB,MAAM;AAC/B,QAAI,CAAC,kBAAmB;AACxB,iBAAa,iBAAiB;AAC9B,wBAAoB;AAAA,EACtB;AAEA,MAAI;AACJ,QAAM,oBAAoB,MAAM;AAC9B,QAAI,CAAC,YAAa;AAClB,kBAAc,WAAW;AACzB,kBAAc;AAAA,EAChB;AACA,QAAM,qBAAqB,MAAM;AAC/B,QAAI,CAAC,mBAAmB,mBAAmB,EAAG;AAC9C,QAAI,YAAa;AACjB,UAAM,SAAS,KAAK;AAAA,MAClB;AAAA,MACA,KAAK,IAAI,KAAM,KAAK,MAAM,kBAAkB,CAAC,CAAC;AAAA,IAChD;AACA,kBAAc,YAAY,MAAM;AAC9B,UAAI,eAAe,WAAY;AAC/B,YAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,UAAI,UAAU,gBAAiB;AAC/B;AAAA,QACE,IAAI;AAAA,UACF,0BAA0B,OAAO,iBAAiB,eAAe;AAAA,QACnE;AAAA,MACF,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAClB,GAAG,MAAM;AAAA,EACX;AAEA,QAAM,uBAAuB,CAC3B,YACG;AACH,QAAI,CAAC,eAAgB;AACrB,QAAI,kBAAmB;AACvB,wBAAoB,WAAW,MAAM;AACnC,0BAAoB;AACpB,UAAI,eAAe;AACjB,gBAAQ,IAAI,MAAM,2BAA2B,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,IAClE,GAAG,cAAc;AAAA,EACnB;AAEA,QAAM,SAAS,QAAQ,aAAa;AAEpC,QAAM,UAAU,OAAO,WAAoC;AACzD,QAAI,YAAa;AACjB,QAAI,WAAY;AAChB,iBAAa;AACb,kBAAc;AAEd,uBAAmB;AAEnB,UAAM,UACH,QAAgB,WAAY,QAAgB,WAAW,KAAK;AAC/D,UAAMC,WAAU,OAAO,QAAQ;AAC/B,UAAM,UACJA,YAAW,OAAOA,aAAY,WAC1B,GAAGA,SAAQ,OAAO,IAAIA,SAAQ,IAAI,KAClC;AACN,QAAI;AACF;AAAA,QACE,qCAAqC,OAAO,YAAY,UAAU,WAAW,OAAO;AAAA,MACtF;AAAA;AAEA;AAAA,QACE,qCAAqC,OAAO,YAAY,UAAU;AAAA,MACpE;AAGF,eAAW,KAAK,MAAM,KAAK,OAAO,GAAG;AACnC,UAAI;AACF,UAAE,QAAQ;AAAA,MACZ,QAAQ;AAAA,MAER;AAAA,IACF;AACA,YAAQ,MAAM;AAEd,QAAI;AACF,YAAM,MAAM;AAAA,IACd,QAAQ;AAAA,IAER;AACA,YAAQ,UAAU;AAGlB,QAAI;AACF,YAAM,YAAY,KAAK;AAAA,IACzB,QAAQ;AAAA,IAER;AACA,QAAI;AACF,YAAM,YAAY,MAAM;AAAA,IAC1B,SAAS,GAAG;AAEV,mBAAa;AACb,YAAM,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACvB;AAAA,IACF;AAIA,QAAI,mBAAmB;AAAA,IAGvB;AAEA,iBAAa;AACb,kBAAc;AACd,QAAI,mCAAmC;AAGvC,QAAI,eAAe,EAAG,sBAAqB,KAAK;AAAA,EAClD;AAEA,QAAM,QAAQ,OAAO,WAAoC;AACvD,QAAI,YAAa;AACjB,kBAAc;AAEd,sBAAkB;AAClB,uBAAmB;AACnB,UAAM,YACH,QAAgB,WAChB,QAAgB,WAAW,KAC5B,UACA;AAEF,UAAM,MAAM;AAEZ,QAAI;AACF,YAAM,YAAY,KAAK;AAAA,IACzB,QAAQ;AAAA,IAER;AAGA,QAAI,kBAAkB;AACpB,UAAI;AACF,cAAM,iBAAiB,QAAQ;AAC/B,2BAAmB,4BAA4B;AAAA,MACjD,QAAQ;AAAA,MAER;AAAA,IACF;AAKA,QAAI,sBAAsB,CAAC,kBAAkB;AAC3C,YAAM,QAAQ;AAAA,QACZ,MAAM,KAAK,WAAW,EAAE,IAAI,OAAO,MAAM;AACvC,cAAI;AACF,kBAAM,EAAE,MAAM;AAAA,UAChB,QAAQ;AAAA,UAER;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI;AACF,aAAO,MAAM;AAAA,IACf,QAAQ;AAAA,IAER;AAEA,QAAI,aAAa,SAAS,GAAG;AAAA,EAC/B;AAEA,SAAO,GAAG,cAAc,CAAC,WAAW;AAClC,kBAAc;AACd,UAAM,SAAS,GAAG,OAAO,iBAAiB,SAAS,IAAI,OAAO,cAAc,SAAS;AAErF,UAAM,cAAc,MAAM;AACxB;AACA,yBAAmB;AACnB,cAAQ,IAAI,MAAM;AAGlB,aAAO,GAAG,QAAQ,MAAM,cAAc,CAAC;AAGvC,UAAI;AACF,cAAM,YAAY,OAAO,MAAM,KAAK,MAAM;AAC1C,QAAC,OAAe,QAAQ,IAAI,SAAgB;AAC1C,wBAAc;AACd,iBAAO,UAAU,GAAG,IAAI;AAAA,QAC1B;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,YAAM,UAAU,MAAM;AACtB,UAAI,qBAAqB,MAAM,UAAU,UAAU,GAAG;AAAA,IACxD;AAEA,QAAI,CAAC,aAAa;AAEhB,kBAAY;AAAA,IACd,OAAO;AAEL,UAAI,gBAAgB;AACpB,UAAI,aAAa,OAAO,MAAM,CAAC;AAC/B,YAAM,cAAc,WAAW,MAAM;AACnC,YAAI,CAAC,eAAe;AAClB,cAAI,yCAAyC,MAAM,GAAG;AACtD,iBAAO,QAAQ;AAAA,QACjB;AAAA,MACF,GAAG,GAAI;AAEP,YAAM,SAAS,CAAC,SAAiB;AAC/B,sBAAc;AAEd,YAAI,CAAC,eAAe;AAClB,uBAAa,OAAO,OAAO,CAAC,YAAY,IAAI,CAAC;AAC7C,gBAAM,aAAa,WAAW,SAAS,MAAM;AAC7C,gBAAM,YAAY,WAAW,MAAM,qBAAqB;AAExD,cAAI,WAAW;AACb,kBAAM,CAAC,EAAE,gBAAgB,cAAc,IAAI;AAC3C,gBAAI,mBAAmB,YAAY,mBAAmB,UAAU;AAC9D,8BAAgB;AAChB,2BAAa,WAAW;AACxB,0BAAY;AAGZ,oBAAM,iBAAiB,UAAU,CAAC,EAAE;AACpC,oBAAM,gBAAgB,WAAW,SAAS,cAAc;AAGxD,qBAAO,eAAe,QAAQ,MAAM;AACpC,qBAAO,GAAG,QAAQ,MAAM,cAAc,CAAC;AAGvC,kBAAI,cAAc,SAAS,GAAG;AAC5B,uBAAO,KAAK,QAAQ,aAAa;AAAA,cACnC;AAAA,YACF,OAAO;AACL,kBAAI,wCAAwC,MAAM,GAAG;AACrD,qBAAO,QAAQ;AACf;AAAA,YACF;AAAA,UACF,WAAW,WAAW,SAAS,MAAM;AAEnC,gBAAI,iDAAiD,MAAM,GAAG;AAC9D,mBAAO,QAAQ;AACf;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,aAAO,GAAG,QAAQ,MAAM;AAAA,IAC1B;AAEA,QAAI,UAAU;AACd,UAAM,MAAM,MAAM;AAChB,UAAI,CAAC,QAAS;AACd,gBAAU;AACV,mBAAa,KAAK,IAAI,GAAG,aAAa,CAAC;AACvC,cAAQ,OAAO,MAAM;AACrB,UAAI,wBAAwB,MAAM,UAAU,UAAU,GAAG;AACzD,UAAI,eAAe,EAAG,sBAAqB,KAAK;AAAA,IAClD;AAEA,WAAO,KAAK,SAAS,GAAG;AACxB,WAAO,KAAK,SAAS,GAAG;AAAA,EAC1B,CAAC;AAGD,MAAI,mBAAmB;AAErB,IAAC,YAAgC;AAAA,MAC/B;AAAA,MACA,CAAC,UAAkB;AACjB,sBAAc;AACd,YAAI;AAGF,cAAI,aAAa;AACjB,cAAI;AAEF,qBAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,kBAAI,MAAM,CAAC,MAAM,KAAQ,MAAM,IAAI,CAAC,MAAM,GAAM;AAC9C,oBAAI,WAAW;AACf,oBAAI,MAAM,IAAI,CAAC,MAAM,GAAM;AACzB,6BAAW,IAAI;AAAA,gBACjB,WAAW,MAAM,IAAI,CAAC,MAAM,KAAQ,MAAM,IAAI,CAAC,MAAM,GAAM;AACzD,6BAAW,IAAI;AAAA,gBACjB;AAEA,oBAAI,YAAY,KAAK,WAAW,MAAM,QAAQ;AAC5C,wBAAM,WAAW,MAAM,QAAQ,KAAK,KAAK;AACzC,sBAAI,YAAY,GAAG;AAEjB,iCAAa;AACb;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AACA,gBAAM,oBAAoB,QAAQ,OAAO,YAAY,MAAS;AAAA,QAChE,SAAS,GAAG;AACV,gBAAM,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,MAAC,YAAgC;AAAA,QAC/B;AAAA,QACA,CAAC,UAAkB;AACjB,wBAAc;AACd,cAAI;AACF,gBAAI,OAAO,SAAS,QAAQ;AAC1B,oBAAM,mBAAmB,KAAK;AAAA,YAChC,OAAO;AACL,oBAAM,qBAAqB,KAAK;AAAA,YAClC;AAAA,UACF,SAAS,GAAG;AACV,kBAAM,CAAC,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AAEL,IAAC,YAAoC;AAAA,MACnC;AAAA,MACA,CAAC,OAAY;AACX,sBAAc;AACd,YAAI;AACF,gBAAM;AAAA,YACJ,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,UACL;AAAA,QACF,SAAS,GAAG;AACV,gBAAM,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,MAAC,YAAoC;AAAA,QACnC;AAAA,QACA,CAAC,UAAkB;AACjB,wBAAc;AACd,cAAI;AACF,gBAAI,OAAO,SAAS,QAAQ;AAC1B,oBAAM,mBAAmB,KAAK;AAAA,YAChC,OAAO;AACL,oBAAM,qBAAqB,KAAK;AAAA,YAClC;AAAA,UACF,SAAS,GAAG;AACV,kBAAM,CAAC,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,cAAY,GAAG,SAAgB,CAAC,MAAe;AAC7C,QAAI,WAAY;AAChB,UAAM,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzB,CAAC;AACD,cAAY,GAAG,SAAgB,CAAC,MAAe;AAC7C,QAAI,WAAY;AAChB,UAAM,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzB,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,GAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,EACxC,CAAC;AAED,QAAM,UAAU,OAAO,QAAQ;AAC/B,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,QAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,+BAA+B;AAE1D,MAAI,mBAAmB,IAAI,IAAI,IAAI,GAAG;AAEtC,QAAM,YAAY,WACd;AAAA,IACE,OAAO;AAAA,IACP,YAAY,SAAS;AAAA,IACrB,UAAU,SAAS;AAAA,EACrB,IACA;AAGJ,uBAAqB,KAAK;AAC1B,qBAAmB;AAEnB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,SAAS;AAAA,IACpB,GAAI,YAAY,EAAE,OAAO,UAAU,IAAI,CAAC;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA2EA,eAAsB,gCACpB,SAC8B;AAC9B,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,aACJ,QAAQ,eACP,SAAS,SAAS,SAAS,IAAI,cAAc;AAEhD,QAAM,WAAW,QAAQ;AAEzB,QAAM,MAAM,CAAC,QAAgB,SAC3B,OAAO;AAAA,IACL,sBAAsB,OAAO,SAAS,QAAQ,KAAK,GAAG;AAAA,IACtD,GAAG;AAAA,EACL;AACF,QAAM,OAAO,CAAC,QAAgB,SAC5B,OAAO;AAAA,IACL,sBAAsB,OAAO,SAAS,QAAQ,KAAK,GAAG;AAAA,IACtD,GAAG;AAAA,EACL;AAEF;AAAA,IACE,+BAA+B,UAAU,GAAG,WAAW,aAAa,QAAQ,KAAK,EAAE;AAAA,EACrF;AAGA,QAAM,eAQF;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,EACF;AACA,MAAI,UAAU,QAAW;AACvB,iBAAa,QAAQ;AAAA,EACvB;AACA,MAAI,aAAa,QAAW;AAC1B,iBAAa,WAAW;AAAA,EAC1B;AAEA,QAAM,EAAE,QAAQ,aAAa,MAAM,WAAW,IAC5C,MAAM,IAAI,2BAA2B,YAAY;AAGnD,MAAI;AASJ,QAAM,kBAAkB,YAMnB;AACH,WAAO,MAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC5C,YAAM,UAAU,WAAW,MAAM;AAC/B,gBAAQ;AACR;AAAA,UACE,IAAI;AAAA,YACF,gDAAgD,QAAQ;AAAA,UAC1D;AAAA,QACF;AAAA,MACF,GAAG,iBAAiB;AAEpB,YAAM,UAAU,CAAC,MAAe;AAC9B,gBAAQ;AACR,eAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,MACtD;AAEA,YAAM,OAAO,CAAC,OAAY;AACxB,YAAI,CAAC,IAAI,WAAY;AACrB,cAAM,YAAY,GAAG;AACrB,cAAM,aAAa,GAAG;AAEtB,YAAI,cAAc,QAAQ;AACxB,gBAAM,EAAE,KAAAF,MAAK,KAAAC,MAAK,eAAe,IAC/B,mCAAmC,UAAU;AAC/C,cAAI,CAACD,QAAO,CAACC,KAAK;AAClB,kBAAQ;AACR,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;AAAA,YAC3C,MAAM,EAAE,KAAAD,MAAK,KAAAC,KAAI;AAAA,UACnB,CAAC;AACD;AAAA,QACF;AAEA,cAAM,EAAE,KAAK,KAAK,IAAI,IACpB,mCAAmC,UAAU;AAC/C,YAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAK;AAC1B,gBAAQ;AACR,gBAAQ,EAAE,WAAW,YAAY,MAAM,EAAE,KAAK,KAAK,IAAI,EAAE,CAAC;AAAA,MAC5D;AAGA,YAAM,eAAe,CAAC,UAAkB;AACtC,YAAI,MAAO;AAEX,YAAI;AACF,gBAAM,OAAO,gBAAgB,KAAK;AAClC,cAAI,MAAM;AACR,oBAAQ;AAAA,cACN,YAAY,KAAK;AAAA,cACjB,UAAU,KAAK;AAAA,cACf,WAAW,KAAK;AAAA,cAChB,MAAM;AAAA,YACR;AACA;AAAA,cACE,+BAA+B,MAAM,UAAU,OAAO,MAAM,QAAQ;AAAA,YACtE;AAAA,UACF;AAAA,QACF,QAAQ;AAEN,cAAI,cAAc;AAChB,kBAAM,YAAY,+BAA+B;AAAA,cAC/C,YAAY,aAAa;AAAA,cACzB,UAAU,aAAa;AAAA,YACzB,CAAC;AACD,gBAAI,WAAW;AACb,sBAAQ;AAAA,gBACN,YAAY,aAAa;AAAA,gBACzB,UAAU,aAAa;AAAA,gBACvB;AAAA,gBACA,MAAM;AAAA,cACR;AACA;AAAA,gBACE,sBAAsB,MAAM,UAAU,OAAO,MAAM,QAAQ;AAAA,cAC7D;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,UAAU,MAAM;AACpB,qBAAa,OAAO;AACpB,oBAAY,eAAe,SAAgB,OAAc;AACzD,oBAAY,eAAe,mBAA0B,IAAW;AAChE,oBAAY,eAAe,cAAqB,YAAmB;AAAA,MACrE;AAEA,kBAAY,GAAG,SAAgB,OAAc;AAC7C,kBAAY,GAAG,mBAA0B,IAAW;AACpD,kBAAY,GAAG,cAAqB,YAAmB;AAAA,IACzD,CAAC;AAAA,EACH;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,gBAAgB;AAAA,EACnC,SAAS,GAAG;AACV,QAAI;AACF,YAAM,WAAW;AAAA,IACnB,QAAQ;AAAA,IAER;AACA,QAAI,oBAAoB;AACtB,UAAI;AACF,cAAM,IAAI,MAAM;AAAA,MAClB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM;AAAA,EACR;AAEA,MAAI,yBAAyB,SAAS,SAAS,EAAE;AAEjD,QAAM,QAAwB;AAAA,IAC5B,WAAW,SAAS;AAAA,IACpB,aAAa;AAAA,IACb,GAAI,SAAS,cAAc,SACvB;AAAA,MACE,MAAM;AAAA,QACJ,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,QACpB,GAAI,SAAS,iBACT,EAAE,gBAAgB,SAAS,eAAe,IAC1C,CAAC;AAAA,MACP;AAAA,IACF,IACA;AAAA,MACE,MAAM;AAAA,QACJ,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,MACtB;AAAA,IACF;AAAA,EACN;AAEA,QAAM,WAAoC,QACtC;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,MAAM;AAAA,IAClB,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,EACnB,IACA;AAEJ,QAAM,MAAM,gBAAgB,OAAO,QAAQ;AAC3C,QAAM,MAAM;AACZ,QAAM,QAAQ,IAAI;AAAA,IAChB;AAAA,IACA;AAAA,IACA,WAAW,mBAAmB;AAAA,IAC9B;AAAA,EACF;AAEA;AAAA,IACE,oBAAoB,SAAS,SAAS,aAAa,gBAAgB,GAAG,WAAW,eAAe,SAAS,UAAU,IAAI,SAAS,QAAQ,OAAO,gBAAgB,KAAK,cAAc;AAAA,EACpL;AAEA,MAAI,aAAa;AACjB,QAAM,UAAU,oBAAI,IAAgB;AACpC,MAAI,cAAc;AAClB,MAAI,cAAc;AAClB,MAAI;AAEJ,QAAM,QAAQ,OAAO,WAAoC;AACvD,QAAI,YAAa;AACjB,kBAAc;AAEd,UAAM,UACH,QAAgB,WAAY,QAAgB,WAAW,KAAK;AAC/D,QAAI,QAAS,KAAI,YAAY,OAAO,EAAE;AAAA,QACjC,KAAI,SAAS;AAElB,QAAI;AACF,YAAM,WAAW;AAAA,IACnB,QAAQ;AAAA,IAER;AAEA,eAAW,KAAK,SAAS;AACvB,UAAI;AACF,UAAE,QAAQ;AAAA,MACZ,QAAQ;AAAA,MAER;AAAA,IACF;AACA,YAAQ,MAAM;AAEd,QAAI;AACF,aAAO,MAAM;AAAA,IACf,QAAQ;AAAA,IAER;AAEA,QAAI,oBAAoB;AACtB,UAAI;AACF,cAAM,IAAI,MAAM;AAAA,MAClB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,aAAa;AAEpC,SAAO,GAAG,cAAc,CAAC,WAAW;AAClC,UAAM,SAAS,GAAG,OAAO,aAAa,IAAI,OAAO,UAAU;AAC3D,QAAI,qBAAqB,MAAM,EAAE;AAEjC,YAAQ,IAAI,MAAM;AAClB;AAEA,UAAM,cAAc,MAAM;AACxB,YAAM,UAAU,MAAM;AAAA,IACxB;AAEA,QAAI,CAAC,aAAa;AAEhB,kBAAY;AAAA,IACd,OAAO;AAEL,UAAI,gBAAgB;AACpB,UAAI,aAAa,OAAO,MAAM,CAAC;AAC/B,YAAM,cAAc,WAAW,MAAM;AACnC,YAAI,CAAC,eAAe;AAClB,cAAI,yCAAyC,MAAM,GAAG;AACtD,iBAAO,QAAQ;AAAA,QACjB;AAAA,MACF,GAAG,GAAI;AAEP,YAAM,SAAS,CAAC,SAAiB;AAC/B,YAAI,CAAC,eAAe;AAClB,uBAAa,OAAO,OAAO,CAAC,YAAY,IAAI,CAAC;AAC7C,gBAAM,aAAa,WAAW,SAAS,MAAM;AAC7C,gBAAM,YAAY,WAAW,MAAM,qBAAqB;AAExD,cAAI,WAAW;AACb,kBAAM,CAAC,EAAE,gBAAgB,cAAc,IAAI;AAC3C,gBAAI,mBAAmB,YAAY,mBAAmB,UAAU;AAC9D,8BAAgB;AAChB,2BAAa,WAAW;AACxB,0BAAY;AACZ,qBAAO,eAAe,QAAQ,MAAM;AAAA,YACtC,OAAO;AACL,kBAAI,wCAAwC,MAAM,GAAG;AACrD,qBAAO,QAAQ;AACf;AAAA,YACF;AAAA,UACF,WAAW,WAAW,SAAS,MAAM;AACnC,gBAAI,iDAAiD,MAAM,GAAG;AAC9D,mBAAO,QAAQ;AACf;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,aAAO,GAAG,QAAQ,MAAM;AAAA,IAC1B;AAEA,QAAI,UAAU;AACd,UAAM,MAAM,MAAM;AAChB,UAAI,CAAC,QAAS;AACd,gBAAU;AACV,mBAAa,KAAK,IAAI,GAAG,aAAa,CAAC;AACvC,cAAQ,OAAO,MAAM;AACrB,UAAI,+BAA+B,MAAM,YAAY,UAAU,GAAG;AAGlE,UAAI,eAAe,KAAK,aAAa;AACnC,cAAM,6BAA6B,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACrD;AAAA,IACF;AAEA,WAAO,KAAK,SAAS,GAAG;AACxB,WAAO,KAAK,SAAS,CAAC,MAAM;AAC1B,WAAK,wBAAwB,MAAM,IAAI,GAAG,WAAW,OAAO,CAAC,CAAC;AAC9D,UAAI;AAAA,IACN,CAAC;AAAA,EACH,CAAC;AAED,SAAO,GAAG,SAAS,CAAC,MAAM;AACxB,SAAK,gBAAgB,GAAG,WAAW,OAAO,CAAC,CAAC;AAC5C,UAAM,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzB,CAAC;AAGD,cAAY,GAAG,mBAA0B,CAAC,OAAY;AACpD,QAAI,YAAa;AACjB,QAAI;AACF,YAAM;AAAA,QACJ,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAG;AAAA,MACL;AAAA,IACF,SAAS,GAAG;AACV,YAAM,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACzB;AAAA,EACF,CAAC;AAGD,MAAI,UAAU;AACZ,gBAAY,GAAG,cAAqB,CAAC,UAAkB;AACrD,UAAI,YAAa;AACjB,UAAI;AACF,YAAI,OAAO,SAAS,QAAQ;AAC1B,gBAAM,mBAAmB,KAAK;AAAA,QAChC,OAAO;AACL,gBAAM,qBAAqB,KAAK;AAAA,QAClC;AAAA,MACF,SAAS,GAAG;AACV,cAAM,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACzB;AAAA,IACF,CAAC;AAAA,EACH;AAGA,cAAY,GAAG,OAAc,MAAM;AACjC,QAAI,wBAAwB;AAC5B,kBAAc;AACd,0BAAsB;AAGtB,QAAI,eAAe,GAAG;AACpB,YAAM,cAAc,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACtC;AAAA,EACF,CAAC;AAED,cAAY,GAAG,SAAgB,CAAC,MAAe;AAC7C,UAAM,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzB,CAAC;AAED,cAAY,GAAG,SAAgB,CAAC,MAAe;AAC7C,QAAI,CAAC,aAAa;AAChB,YAAM,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACzB;AAAA,EACF,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,GAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,EACxC,CAAC;AAED,QAAM,UAAU,OAAO,QAAQ;AAC/B,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,QAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,0CAA0C;AAErE,MAAI,mBAAmB,IAAI,IAAI,IAAI,GAAG;AAEtC,QAAM,YAAY,WACd;AAAA,IACE,OAAO;AAAA,IACP,YAAY,SAAS;AAAA,IACrB,UAAU,SAAS;AAAA,EACrB,IACA;AAEJ,QAAM,SAA8B;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,SAAS;AAAA,IACpB,GAAI,YAAY,EAAE,OAAO,UAAU,IAAI,CAAC;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,SAAO,eAAe,QAAQ,eAAe;AAAA,IAC3C,KAAK,MAAM;AAAA,IACX,KAAK,CAAC,OAAiC;AACrC,4BAAsB;AAAA,IACxB;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AEviEA,OAAOE,WAAU;AACjB,SAAS,SAAAC,cAAgC;AACzC,SAAS,mBAA6B;AAsDtC,eAAsB,uBACpB,SAC2B;AAC3B,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,OAAO;AAAA,IACP;AAAA,EACF,IAAI;AAGJ,QAAM,aACJ,QAAQ,eACP,SAAS,SAAS,SAAS,IAAI,cAAc;AAEhD,QAAM,MAAM,CAAC,QACX,OAAO,IAAI,kBAAkB,OAAO,SAAS,SAAS,MAAM,GAAG,CAAC,KAAK,GAAG,EAAE;AAC5E,QAAM,OAAO,CAAC,QACZ,OAAO;AAAA,IACL,kBAAkB,OAAO,SAAS,SAAS,MAAM,GAAG,CAAC,KAAK,GAAG;AAAA,EAC/D;AAEF,MAAI,+BAA+B,UAAU,EAAE;AAG/C,QAAM,aAA4B,CAAC;AACnC,MAAI,cAAc;AAClB,MAAI,aAA8B;AAClC,MAAI,WAAW;AACf,MAAI,wBAAwB;AAG5B,QAAM,eAOF;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,EACF;AACA,MAAI,UAAU,QAAW;AACvB,iBAAa,QAAQ;AAAA,EACvB;AAEA,QAAM,EAAE,QAAQ,aAAa,MAAM,WAAW,IAC5C,MAAM,IAAI,2BAA2B,YAAY;AAGnD,QAAM,eAAe,CAAC,OAAY;AAChC,QAAI,CAAC,IAAI,KAAM;AAEf,UAAM,QAAqB;AAAA,MACzB,MAAM;AAAA,MACN,MAAM,GAAG;AAAA,MACT,YAAY,GAAG;AAAA,MACf,WAAW,GAAG;AAAA,MACd,WAAW,GAAG;AAAA,IAChB;AAEA,eAAW,KAAK,KAAK;AAErB,QAAI,GAAG,cAAc,QAAQ;AAC3B,mBAAa;AAAA,IACf;AAEA,QAAI,GAAG,cAAc,CAAC,uBAAuB;AAC3C,8BAAwB;AACxB;AAAA,QACE,kCAAkC,UAAU,YAAY,WAAW,MAAM;AAAA,MAC3E;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,CAAC,UAAkB;AACtC,QAAI,CAAC,OAAO,OAAQ;AACpB,eAAW;AAEX,eAAW,KAAK;AAAA,MACd,MAAM;AAAA,MACN,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,QAAM,cAAc,MAAM;AACxB,QAAI,sCAAsC,WAAW,MAAM,EAAE;AAC7D,kBAAc;AAAA,EAChB;AAEA,cAAY,GAAG,mBAA0B,YAAY;AACrD,cAAY,GAAG,cAAqB,YAAY;AAChD,cAAY,GAAG,OAAc,WAAW;AACxC,cAAY,GAAG,SAAgB,MAAM;AACnC,QAAI,CAAC,aAAa;AAChB,oBAAc;AAAA,IAChB;AAAA,EACF,CAAC;AAGD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,UAAU,WAAW,MAAM;AAC/B,aAAO,IAAI,MAAM,oCAAoC,CAAC;AAAA,IACxD,GAAG,GAAM;AAET,UAAM,QAAQ,MAAM;AAClB,UAAI,uBAAuB;AACzB,qBAAa,OAAO;AACpB,gBAAQ;AAAA,MACV,WAAW,aAAa;AACtB,qBAAa,OAAO;AACpB,eAAO,IAAI,MAAM,oCAAoC,CAAC;AAAA,MACxD,OAAO;AACL,mBAAW,OAAO,EAAE;AAAA,MACtB;AAAA,IACF;AACA,UAAM;AAAA,EACR,CAAC;AAED;AAAA,IACE,yBAAyB,UAAU,cAAc,QAAQ,YAAY,WAAW,MAAM;AAAA,EACxF;AAGA,MAAI,aAAiC;AACrC,MAAI,gBAAqC;AACzC,MAAI,SAAS;AAEb,QAAM,QAAQ,YAAY;AACxB,QAAI,OAAQ;AACZ,aAAS;AAET,QAAI,YAAY;AAEhB,gBAAY,eAAe,mBAA0B,YAAY;AACjE,gBAAY,eAAe,cAAqB,YAAY;AAC5D,gBAAY,eAAe,OAAc,WAAW;AAEpD,QAAI;AACF,YAAM,WAAW;AAAA,IACnB,QAAQ;AAAA,IAER;AAEA,QAAI,eAAe;AACjB,UAAI;AACF,sBAAc,KAAK,SAAS;AAAA,MAC9B,QAAQ;AAAA,MAER;AACA,sBAAgB;AAAA,IAClB;AAEA,QAAI,YAAY;AACd,iBAAW,MAAM;AACjB,mBAAa;AAAA,IACf;AAAA,EACF;AAGA,eAAaD,MAAK,aAAa,CAAC,KAAK,QAAQ;AAC3C,QAAI,QAAQ;AACV,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,eAAe;AACvB;AAAA,IACF;AAEA,QAAI,iBAAiB,IAAI,MAAM,IAAI,IAAI,GAAG,EAAE;AAG5C,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB;AAAA,MAChB,qBAAqB;AAAA,MACrB,iBAAiB;AAAA,MACjB,YAAY;AAAA,IACd,CAAC;AAGD,UAAM,eAAe,IAAI,YAAY;AAKrC,UAAM,aAAa,eAAe,SAAS,SAAS;AACpD,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,QAAI,oBAAoB,UAAU,IAAI,WAAW,KAAK,GAAG,CAAC,EAAE;AAE5D,oBAAgBC,OAAM,YAAY,YAAY;AAAA,MAC5C,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAGD,kBAAc,QAAQ,KAAK,YAAY,EAAE,KAAK,GAAG;AAGjD,kBAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACjD,YAAM,MAAM,KAAK,SAAS,EAAE,KAAK;AACjC,UAAI,KAAK;AACP,aAAK,WAAW,GAAG,EAAE;AAAA,MACvB;AAAA,IACF,CAAC;AAED,kBAAc,GAAG,SAAS,CAAC,QAAQ;AACjC,WAAK,iBAAiB,IAAI,OAAO,EAAE;AACnC,UAAI,IAAI;AAAA,IACV,CAAC;AAED,kBAAc,GAAG,SAAS,CAAC,SAAS;AAClC,UAAI,2BAA2B,IAAI,EAAE;AACrC,UAAI,IAAI;AAAA,IACV,CAAC;AAGD,QAAI,GAAG,SAAS,MAAM;AACpB,UAAI,qBAAqB;AACzB,UAAI,eAAe;AACjB,YAAI;AACF,wBAAc,OAAO,IAAI;AACzB,wBAAc,KAAK,SAAS;AAAA,QAC9B,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM,aAAa,YAAY;AAC7B,YAAM,QAAQ,eAAe;AAC7B,UAAI,CAAC,MAAO;AAGZ,UAAI,aAAa;AACjB,iBAAW,SAAS,YAAY;AAC9B,YAAI,MAAM,SAAS,WAAW,MAAM,MAAM;AACxC,cAAI;AACF,kBAAM,WAAW,MAAM,MAAM,MAAM,IAAI;AACvC,gBAAI,CAAC,UAAU;AACb,oBAAM,IAAI;AAAA,gBAAc,CAAC,YACvB,MAAM,KAAK,SAAS,OAAO;AAAA,cAC7B;AAAA,YACF;AAAA,UACF,SAAS,GAAG;AACV,iBAAK,8BAA8B,UAAU,KAAK,CAAC,EAAE;AACrD;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AAEA,UAAI,OAAO,UAAU,0BAA0B;AAG/C,YAAM,mBAAmB,CAAC,OAAY;AACpC,YAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,SAAU;AAClC,YAAI;AACF,gBAAM,MAAM,GAAG,IAAI;AAAA,QACrB,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,kBAAY,GAAG,mBAA0B,gBAAgB;AAGzD,YAAM,aAAa,MAAM;AACvB,YAAI,uCAAuC;AAC3C,oBAAY,eAAe,mBAA0B,gBAAgB;AACrE,YAAI;AACF,gBAAM,IAAI;AAAA,QACZ,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,UAAI,aAAa;AACf,mBAAW;AAAA,MACb,OAAO;AACL,oBAAY,KAAK,OAAc,UAAU;AACzC,oBAAY,KAAK,SAAgB,UAAU;AAAA,MAC7C;AAAA,IACF;AAGA,eAAW,EAAE,MAAM,CAAC,MAAM;AACxB,WAAK,yBAAyB,CAAC,EAAE;AAAA,IACnC,CAAC;AAAA,EACH,CAAC;AAGD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,eAAY,KAAK,SAAS,MAAM;AAChC,eAAY,OAAO,GAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,EAC7C,CAAC;AAED,QAAM,UAAU,WAAW,QAAQ;AACnC,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AACA,QAAM,OAAO,QAAQ;AAErB,QAAM,MAAM,UAAU,IAAI,IAAI,IAAI;AAClC,MAAI,0BAA0B,GAAG,EAAE;AAEnC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AClXA,SAAS,gBAAAC,qBAAoB;AAC7B,YAAY,SAAS;AAkBrB,IAAM,oBAAN,MAA2B;AAAA,EACR;AAAA,EACA,QAAa,CAAC;AAAA,EACvB;AAAA,EAGA,SAAS;AAAA,EAEjB,YAAY,UAAkB;AAC5B,SAAK,WAAW,KAAK,IAAI,GAAG,WAAW,CAAC;AAAA,EAC1C;AAAA,EAEA,KAAK,MAAe;AAClB,QAAI,KAAK,OAAQ;AACjB,QAAI,KAAK,SAAS;AAChB,YAAM,EAAE,QAAQ,IAAI,KAAK;AACzB,WAAK,UAAU;AACf,cAAQ,EAAE,OAAO,MAAM,MAAM,MAAM,CAAC;AACpC;AAAA,IACF;AACA,SAAK,MAAM,KAAK,IAAI;AACpB,QAAI,KAAK,MAAM,SAAS,KAAK,UAAU;AACrC,WAAK,MAAM,OAAO,GAAG,KAAK,MAAM,SAAS,KAAK,QAAQ;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,QAAI,KAAK,SAAS;AAChB,YAAM,EAAE,QAAQ,IAAI,KAAK;AACzB,WAAK,UAAU;AACf,cAAQ,EAAE,OAAO,QAAkB,MAAM,KAAK,CAAC;AAAA,IACjD;AAAA,EACF;AAAA,EAEA,MAAM,OAAmC;AACvC,QAAI,KAAK,OAAQ,QAAO,EAAE,OAAO,QAAkB,MAAM,KAAK;AAC9D,UAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,QAAI,SAAS,OAAW,QAAO,EAAE,OAAO,MAAM,MAAM,MAAM;AAC1D,WAAO,MAAM,IAAI,QAA2B,CAAC,YAAY;AACvD,WAAK,UAAU,EAAE,QAAQ;AAAA,IAC3B,CAAC;AAAA,EACH;AACF;AAwBA,IAAM,qBAAN,MAAyB;AAAA,EACN;AAAA,EACA,SAAS,oBAAI,IAA4C;AAAA,EAClE,SAA4D;AAAA,EAC5D,UAAU;AAAA,EACV,cAAoC;AAAA,EAE5C,YAAY,MAAqB;AAC/B,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,SAAS,KAAK,KAAK,aAAa;AAErC,SAAK,eAAe,YAAY;AAC9B,UAAI;AACF,yBAAiB,SAAS,KAAK,QAAS;AACtC,cAAI;AACF,iBAAK,KAAK,UAAU,KAAK;AAAA,UAC3B,QAAQ;AAAA,UAER;AACA,qBAAW,KAAK,KAAK,OAAO,OAAO,GAAG;AACpC,cAAE,KAAK,KAAK;AAAA,UACd;AAAA,QACF;AAAA,MACF,SAAS,GAAG;AACV,aAAK,KAAK,UAAU,CAAC;AAAA,MACvB,UAAE;AACA,mBAAW,KAAK,KAAK,OAAO,OAAO,EAAG,GAAE,MAAM;AAC9C,aAAK,OAAO,MAAM;AAClB,aAAK,UAAU;AACf,aAAK,KAAK,QAAQ;AAAA,MACpB;AAAA,IACF,GAAG;AAAA,EACL;AAAA,EAEA,UAAU,IAAwD;AAChE,UAAM,IAAI,IAAI,kBAA+B,KAAK,KAAK,aAAa;AACpE,SAAK,OAAO,IAAI,IAAI,CAAC;AACrB,UAAM,OAAO;AACb,YAAQ,mBAAmB;AACzB,UAAI;AACF,eAAO,MAAM;AACX,gBAAM,IAAI,MAAM,EAAE,KAAK;AACvB,cAAI,EAAE,KAAM;AACZ,gBAAM,EAAE;AAAA,QACV;AAAA,MACF,UAAE;AACA,UAAE,MAAM;AACR,aAAK,OAAO,OAAO,EAAE;AAAA,MACvB;AAAA,IACF,GAAG;AAAA,EACL;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,UAAU;AACf,UAAM,MAAM,KAAK;AACjB,SAAK,SAAS;AACd,eAAW,KAAK,KAAK,OAAO,OAAO,EAAG,GAAE,MAAM;AAC9C,SAAK,OAAO,MAAM;AAClB,QAAI;AACF,YAAM,KAAK,OAAO,MAAgB;AAAA,IACpC,QAAQ;AAAA,IAER;AACA,QAAI;AACF,YAAM,KAAK;AAAA,IACb,QAAQ;AAAA,IAER;AACA,SAAK,cAAc;AAAA,EACrB;AACF;AAoEO,IAAM,kBAAN,MAAM,yBAAwBC,cAMlC;AAAA,EACgB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,SAAS;AAAA,EACT;AAAA,EACA;AAAA;AAAA,EAGA,eAA0C;AAAA,EAC1C,qBAAqB;AAAA,EACrB;AAAA,EACA;AAAA;AAAA,EAGA,mBAAmB,oBAAI,IAAY;AAAA,EACnC,gBAAgB,oBAAI,IAAwB;AAAA,EAC5C;AAAA;AAAA,EAGA,cAAc;AAAA,EACd;AAAA,EACA,sBAAsB;AAAA,EACtB,0BAA0B;AAAA;AAAA,EAG1B,YAA8B,CAAC;AAAA;AAAA;AAAA,EAI/B,YAKG;AAAA,EAEX,YAAY,SAAiC;AAC3C,UAAM;AACN,SAAK,MAAM,QAAQ;AACnB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,SAAS,QAAQ,UAAU;AAChC,SAAK,WAAW,QAAQ;AACxB,SAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,SAAK,iBAAiB,QAAQ,eAAe;AAC7C,SAAK,iBAAiB,QAAQ,kBAAkB;AAChD,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,iBAAiB,QAAQ,kBAAkB;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AAEd,SAAK,SAAa,iBAAa,CAAC,WAAW,KAAK,aAAa,MAAM,CAAC;AACpE,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,OAAO,QAAQ,mCAAmC,IAAI,OAAO,EAAE;AACpE,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB,CAAC;AAED,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAK,OAAQ,OAAO,KAAK,YAAY,KAAK,YAAY,MAAM;AAC1D,cAAM,OAAO,KAAK,OAAQ,QAAQ;AAClC,aAAK,eAAe,KAAK;AACzB,aAAK,OAAO;AAAA,UACV,kCAAkC,KAAK,OAAO,IAAI,KAAK,IAAI,aAAa,KAAK,OAAO,YAAY,KAAK,OAAO;AAAA,QAC9G;AACA,aAAK,KAAK,aAAa,EAAE,MAAM,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAC9D,gBAAQ;AAAA,MACV,CAAC;AACD,WAAK,OAAQ,KAAK,SAAS,MAAM;AAAA,IACnC,CAAC;AAID,QAAI,KAAK,gBAAgB;AACvB,WAAK,OAAO;AAAA,QACV,yDAAyD,KAAK,OAAO,YAAY,KAAK,OAAO;AAAA,MAC/F;AACA,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,SAAS;AACd,iBAAa,KAAK,cAAc;AAChC,SAAK,wBAAwB;AAG7B,eAAW,CAAC,IAAI,IAAI,KAAK,KAAK,eAAe;AAC3C,WAAK,QAAQ;AACb,WAAK,iBAAiB,OAAO,EAAE;AAAA,IACjC;AACA,SAAK,cAAc,MAAM;AAGzB,UAAM,KAAK,iBAAiB;AAG5B,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAK,OAAQ,MAAM,MAAM,QAAQ,CAAC;AAAA,MACpC,CAAC;AACD,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,YAAY,CAAC;AAClB,SAAK,eAAe;AACpB,SAAK,KAAK,OAAO;AAAA,EACnB;AAAA;AAAA,EAGA,IAAI,OAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,kBAAsC;AACxC,QAAI,KAAK,gBAAgB,KAAM,QAAO;AACtC,WAAO,mBAAmB,KAAK,YAAY;AAAA,EAC7C;AAAA;AAAA,EAGA,IAAI,cAAsB;AACxB,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,oBACJ,IACqD;AACrD,SAAK,iBAAiB,IAAI,QAAQ,EAAE,EAAE;AACtC,QAAI,CAAC,KAAK,oBAAoB;AAC5B,YAAM,KAAK,kBAAkB;AAAA,IAC/B;AACA,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,iBAAiB,OAAO,QAAQ,EAAE,EAAE;AACzC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,aAAa,UAAU,QAAQ,EAAE,EAAE;AAAA,EACjD;AAAA;AAAA,EAGA,sBAAsB,IAAkB;AACtC,SAAK,aAAa,QAAQ,EAAE,IAAI,wBAAwB;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAKS;AACP,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,QAA0B;AAC7C,UAAM,WAAW,GAAG,OAAO,aAAa,IAAI,OAAO,UAAU;AAC7D,WAAO,WAAW,IAAI;AAEtB,SAAK,iBAAiB,IAAI,QAAQ;AAClC,SAAK,cAAc,IAAI,UAAU,MAAM;AACvC,SAAK,OAAO;AAAA,MACV,0CAA0C,QAAQ,UAAU,KAAK,iBAAiB,IAAI;AAAA,IACxF;AACA,SAAK,KAAK,UAAU,QAAQ;AAG5B,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AAOA,QAAI,CAAC,KAAK,oBAAoB;AAC5B,WAAK,kBAAkB;AAAA,IACzB;AAGA,SAAK,WAAW,UAAU,MAAM,EAAE,MAAM,CAAC,QAAQ;AAC/C,WAAK,OAAO;AAAA,QACV,0CAA0C,QAAQ,KAAK,GAAG;AAAA,MAC5D;AAAA,IACF,CAAC;AAED,UAAM,UAAU,CAAC,WAAoB;AACnC,WAAK,aAAa,UAAU,MAAM;AAClC,aAAO,QAAQ;AAAA,IACjB;AACA,WAAO,GAAG,SAAS,CAAC,QAAQ,QAAQ,UAAU,IAAI,OAAO,EAAE,CAAC;AAC5D,WAAO,GAAG,SAAS,CAAC,aAAa,QAAQ,WAAW,uBAAuB,eAAe,CAAC;AAAA,EAC7F;AAAA,EAEA,MAAc,WAAW,UAAkB,QAAmC;AAG5E,UAAM,iBAAiB,KAAK,IAAI,IAAI;AACpC,WAAO,KAAK,UAAU,CAAC,KAAK,cAAc;AACxC,UAAI,OAAO,UAAW;AACtB,UAAI,KAAK,IAAI,IAAI,gBAAgB;AAC/B,aAAK,OAAO;AAAA,UACV,iEAAiE,QAAQ;AAAA,QAC3E;AACA;AAAA,MACF;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,IAC7C;AACA,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,aAAc;AAExC,UAAM,eAAe,KAAK,aAAa,UAAU,QAAQ;AAIzD,QAAI,QAA4B;AAGhC,UAAM,gBAAgB,KAAK,UAAU,MAAM;AAC3C,QAAI,aAAa;AACjB,aAAS,IAAI,cAAc,SAAS,GAAG,KAAK,GAAG,KAAK;AAClD,UAAI,cAAc,CAAC,EAAG,YAAY;AAChC,qBAAa;AACb;AAAA,MACF;AAAA,IACF;AAEA,QAAI,cAAc,GAAG;AACnB,YAAM,SAAS,cAAc,MAAM,UAAU;AAC7C,WAAK,OAAO;AAAA,QACV,8CAA8C,QAAQ,WAAW,OAAO,MAAM;AAAA,MAChF;AAEA,UAAI,CAAC,OAAO;AACV,gBAAQ,IAAI,YAAY;AAAA,UACtB,WAAW,KAAK,qBAAqB;AAAA,UACrC,cAAc;AAAA,QAChB,CAAC;AAAA,MACH;AACA,iBAAW,SAAS,QAAQ;AAC1B,YAAI,OAAO,UAAW;AACtB,YAAI;AACJ,YAAI,CAAC,MAAM,OAAO;AAChB,eAAK,MAAM,SAAS,MAAM,MAAM,MAAM,KAAK,MAAM,UAAU;AAAA,QAC7D,OAAO;AACL,eAAK,MAAM,SAAS,MAAM,MAAM,MAAM,GAAG;AAAA,QAC3C;AACA,YAAI,GAAG,SAAS,EAAG,QAAO,MAAM,EAAE;AAAA,MACpC;AAAA,IACF;AAGA,QAAI,eAAe,cAAc;AACjC,QAAI,iBAAiB;AACrB,QAAI,mBAAmB;AACvB,QAAI,YAAY,KAAK,IAAI;AACzB,QAAI;AACF,WAAK,OAAO;AAAA,QACV,gDAAgD,QAAQ,iBAAiB,YAAY;AAAA,MACvF;AACA,uBAAiB,SAAS,cAAc;AACtC,YAAI,OAAO,aAAa,CAAC,KAAK,QAAQ;AACpC,eAAK,OAAO;AAAA,YACV,4CAA4C,QAAQ,cAAc,OAAO,SAAS,WAAW,KAAK,MAAM;AAAA,UAC1G;AACA;AAAA,QACF;AAEA;AAEA,YAAI,MAAM,OAAO;AAEf,cAAI,OAAO;AACT,kBAAMC,OAAM,MAAM,gBAAgB,KAAK,IAAI,IAAI;AAC/C,kBAAMC,MAAK,MAAM,SAAS,MAAM,MAAMD,IAAG;AACzC,gBAAIC,IAAG,SAAS,EAAG,QAAO,MAAMA,GAAE;AAAA,UACpC;AACA;AAAA,QACF;AAGA,cAAM,SAAS,KAAK,kBAAkB,KAAK;AAC3C,YAAI,CAAC,OAAQ;AAEb,cAAM,OAAO,KAAK,iBAAiB,QAAQ,MAAM,SAAS;AAG1D,YAAI,CAAC,cAAc;AACjB,cAAI,CAAC,KAAM;AACX,yBAAe;AACf,eAAK,OAAO;AAAA,YACV,iDAAiD,QAAQ,UAAU,cAAc;AAAA,UACnF;AAEA,cAAI,CAAC,OAAO;AACV,oBAAQ,IAAI,YAAY;AAAA,cACtB,WAAW,MAAM,aAAa,KAAK,qBAAqB;AAAA,cACxD,cAAc;AAAA,YAChB,CAAC;AAAA,UACH;AAAA,QACF;AAEA,cAAM,MAAM,MAAM,gBAAgB,KAAK,IAAI,IAAI;AAC/C,cAAM,KAAK,MAAO,SAAS,QAAQ,KAAK,IAAI;AAC5C,eAAO,MAAM,EAAE;AAEf;AACA,aAAK;AAGL,YAAI,KAAK,IAAI,IAAI,YAAY,KAAQ;AACnC,eAAK,OAAO;AAAA,YACV,wCAAwC,QAAQ,aAAa,cAAc,YAAY,gBAAgB,WAAW,OAAO,cAAc;AAAA,UACzI;AACA,sBAAY,KAAK,IAAI;AAAA,QACvB;AAGA,YAAI,OAAO,iBAAiB,KAAK,gBAAgB;AAC/C,eAAK,OAAO;AAAA,YACV,sCAAsC,OAAO,cAAc,4BAA4B,QAAQ;AAAA,UACjG;AACA,iBAAO,QAAQ;AACf;AAAA,QACF;AAAA,MACF;AACA,WAAK,OAAO;AAAA,QACV,uDAAuD,QAAQ,aAAa,cAAc,YAAY,gBAAgB;AAAA,MACxH;AAAA,IACF,UAAE;AACA,YAAM,aAAa,OAAO,MAAgB,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,kBAAkB,OAAmC;AAC3D,QAAI,MAAM,MAAO,QAAO;AACxB,QAAI,MAAM,KAAK,WAAW,EAAG,QAAO;AACpC,QAAI;AACF,UAAI,MAAM,cAAc,QAAQ;AAC9B,eAAO,gBAAoB,MAAM,IAAI;AAAA,MACvC;AACA,UAAI,MAAM,cAAc,QAAQ;AAC9B,eAAOC,iBAAoB,MAAM,IAAI;AAAA,MACvC;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,WAAO,MAAM;AAAA,EACf;AAAA;AAAA,EAGQ,iBACN,QACA,WACS;AACT,QAAI;AACF,UAAI,cAAc,QAAQ;AACxB,cAAM,OAAO,iBAAgB,gBAAgB,MAAM;AACnD,eAAO,KAAK,KAAK,CAAC,MAAM,EAAE,UAAU,MAAM,EAAE,CAAC,IAAK,QAAU,CAAC;AAAA,MAC/D;AACA,UAAI,cAAc,QAAQ;AACxB,cAAM,OAAOC,0BAAyB,MAAM;AAC5C,eAAO,KAAK;AAAA,UACV,CAAC,MAAM,EAAE,UAAU,KAAK,WAAY,EAAE,CAAC,KAAM,IAAK,EAAI;AAAA,QACxD;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,OAAe,gBAAgB,KAAuB;AACpD,UAAM,OAAiB,CAAC;AACxB,QAAI,IAAI;AACR,WAAO,IAAI,IAAI,QAAQ;AAErB,UACE,IAAI,IAAI,IAAI,UACZ,IAAI,CAAC,MAAM,KACX,IAAI,IAAI,CAAC,MAAM,GACf;AACA,YAAI;AACJ,YAAI,IAAI,IAAI,CAAC,MAAM,GAAG;AACpB,kBAAQ;AAAA,QACV,WAAW,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG;AACrE,kBAAQ;AAAA,QACV,OAAO;AACL;AACA;AAAA,QACF;AAEA,cAAM,WAAW,IAAI;AACrB,YAAI,SAAS,IAAI;AACjB,iBAAS,IAAI,UAAU,IAAI,IAAI,SAAS,GAAG,KAAK;AAC9C,cACE,IAAI,CAAC,MAAM,KACX,IAAI,IAAI,CAAC,MAAM,MACd,IAAI,IAAI,CAAC,MAAM,KAAM,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,IAC/E;AACA,qBAAS;AACT;AAAA,UACF;AAAA,QACF;AACA,YAAI,SAAS,UAAU;AACrB,eAAK,KAAK,IAAI,SAAS,UAAU,MAAM,CAAC;AAAA,QAC1C;AACA,YAAI;AAAA,MACN,OAAO;AACL;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAe,eAAe,GAAoB;AAChD,WAAO,EAAE,UAAU,KAAK,EAAE,CAAC,MAAM,QAAS,EAAE,CAAC,IAAK,SAAU;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAe,sBACb,GACoE;AACpE,QAAI,EAAE,SAAS,EAAG,QAAO;AACzB,QAAI,CAAC,iBAAgB,eAAe,CAAC,EAAG,QAAO;AAE/C,UAAM,gBAAiB,EAAE,CAAC,KAAM,IAAK;AACrC,UAAM,cAAc;AAAA,MAClB;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAC/D;AAAA,MAAO;AAAA,MAAM;AAAA,IACf;AACA,UAAM,aAAa,YAAY,aAAa,KAAK;AACjD,QAAI,CAAC,WAAY,QAAO;AAExB,UAAM,iBAAkB,EAAE,CAAC,IAAK,MAAS,IAAO,EAAE,CAAC,KAAM,IAAK;AAC9D,UAAM,WAAW,kBAAkB,IAAI,IAAI;AAE3C,UAAM,UAAW,EAAE,CAAC,KAAM,IAAK;AAC/B,UAAM,kBAAkB,UAAU;AAClC,UAAM,MACH,mBAAmB,KAAO,iBAAiB,IAAM,iBAAiB;AACrE,UAAM,YAAY,OAAO,KAAK,CAAE,OAAO,IAAK,KAAM,MAAM,GAAI,CAAC,EAAE;AAAA,MAC7D;AAAA,IACF;AACA,WAAO,EAAE,YAAY,UAAU,UAAU;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,oBAAmC;AAC/C,QAAI,KAAK,mBAAoB;AAM7B,QAAI,CAAC,KAAK,IAAI,SAAS;AACrB,UAAI,KAAK,IAAI,UAAU;AACrB,aAAK,OAAO;AAAA,UACV;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI;AACF,aAAK,OAAO;AAAA,UACV;AAAA,QACF;AACA,cAAM,KAAK,IAAI,gBAAgB;AAAA,MACjC,SAAS,GAAG;AACV,aAAK,OAAO;AAAA,UACV,oEAAoE,CAAC;AAAA,QACvE;AACA;AAAA,MACF;AAAA,IACF;AAEA,SAAK,qBAAqB;AAG1B,QAAI;AACJ,QAAI,KAAK,UAAU;AACjB,UAAI;AAKF,cAAM,UAAU,MAAM,KAAK,IAAI;AAAA,UAC7B,QAAQ,KAAK,QAAQ,MAAM,KAAK,OAAO,IAAI,KAAK,OAAO;AAAA,QACzD;AACA,0BAAkB,QAAQ;AAC1B,aAAK,0BAA0B,QAAQ;AAAA,MACzC,SAAS,GAAG;AACV,aAAK,OAAO;AAAA,UACV,+EAA+E,CAAC;AAAA,QAClF;AAAA,MACF;AAAA,IACF;AAEA,SAAK,OAAO;AAAA,MACV,qDAAqD,KAAK,OAAO,YAAY,KAAK,OAAO,cAAc,CAAC,CAAC,eAAe;AAAA,IAC1H;AAKA,QAAI,YAAY;AAEhB,SAAK,eAAe,IAAI,mBAAmB;AAAA,MACzC,eAAe;AAAA,MACf,cAAc,MACZ,mBAAmB,KAAK,KAAK,KAAK,SAAS,KAAK,SAAS;AAAA,QACvD,SAAS,KAAK;AAAA,QACd,GAAI,kBAAkB,EAAE,QAAQ,gBAAgB,IAAI,CAAC;AAAA,MACvD,CAAC;AAAA,MACH,SAAS,CAAC,UAAU;AAElB,oBAAY;AACZ,aAAK,cAAc,KAAK,IAAI;AAC5B,aAAK;AAGL,YAAI,CAAC,MAAM,UAAU,MAAM,cAAc,UAAU,MAAM,cAAc,SAAS;AAC9E,eAAK,oBAAoB,MAAM;AAAA,QACjC;AAIA,YAAI;AACJ,YAAI;AAEJ,YAAI,MAAM,OAAO;AACf,cAAI,MAAM,KAAK,WAAW,EAAG;AAG7B,cAAI,CAAC,KAAK,WAAW;AACnB,kBAAM,SAAS,iBAAgB,sBAAsB,MAAM,IAAI;AAC/D,gBAAI,QAAQ;AACV,mBAAK,YAAY,EAAE,OAAO,YAAY,GAAG,OAAO;AAAA,YAClD;AAAA,UACF;AACA,uBAAa,MAAM;AACnB,uBAAa;AAAA,QACf,OAAO;AACL,gBAAM,SAAS,KAAK,kBAAkB,KAAK;AAC3C,cAAI,CAAC,UAAU,OAAO,WAAW,EAAG;AACpC,uBAAa;AACb,uBAAa,KAAK,iBAAiB,QAAQ,MAAM,SAAS;AAAA,QAC5D;AAEA,cAAM,MAAM,MAAM,gBAAgB,KAAK,IAAI,IAAI;AAC/C,aAAK,UAAU,KAAK;AAAA,UAClB,MAAM,OAAO,KAAK,UAAU;AAAA,UAC5B,MAAM,KAAK,IAAI;AAAA,UACf;AAAA,UACA,OAAO,MAAM;AAAA,UACb;AAAA,QACF,CAAC;AAGD,cAAM,SAAS,KAAK,IAAI,IAAI,KAAK;AACjC,YAAI,UAAU;AACd,eAAO,UAAU,KAAK,UAAU,UAAU,KAAK,UAAU,OAAO,EAAG,OAAO,QAAQ;AAChF;AAAA,QACF;AACA,YAAI,UAAU,EAAG,MAAK,UAAU,OAAO,GAAG,OAAO;AAAA,MACnD;AAAA,MACA,SAAS,CAAC,UAAU;AAClB,aAAK,OAAO,OAAO,0CAA0C,KAAK,EAAE;AAAA,MACtE;AAAA,MACA,OAAO,MAAM;AACX,YAAI,CAAC,KAAK,mBAAoB;AAC9B,aAAK,qBAAqB;AAC1B,aAAK,eAAe;AACpB,aAAK,wBAAwB;AAE7B,cAAM,YAAY,KAAK,cAAc,IAAI,KAAK,IAAI,IAAI,KAAK,cAAc;AACzE,cAAM,YAAY,YAAY,KAAK,kBAC/B,kCACA,aAAa,IACX,yBACA;AACN,aAAK,OAAO;AAAA,UACV,qDAAqD,SAAS,eACjD,aAAa,IAAI,IAAI,YAAY,KAAM,QAAQ,CAAC,CAAC,UAAU,OAAO,YACpE,KAAK,mBAAmB,YAAY,KAAK,iBAAiB,IAAI;AAAA,QAC3E;AAGA,YAAI,KAAK,yBAAyB;AAChC,eAAK,wBAAwB,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAC7C,eAAK,0BAA0B;AAAA,QACjC;AAaA,YAAI,CAAC,KAAK,gBAAgB;AACxB,eAAK,OAAO;AAAA,YACV,4DAA4D,SAAS,YAC1D,KAAK,OAAO,YAAY,KAAK,OAAO,oBAAe,KAAK,iBAAiB,IAAI;AAAA,UAC1F;AACA,qBAAW,CAAC,EAAE,IAAI,KAAK,KAAK,eAAe;AACzC,iBAAK,QAAQ;AAAA,UACf;AAAA,QACF,WAAW,KAAK,QAAQ;AAMtB,cACE,OAAO,KAAK,IAAI,4BAA4B,cAC5C,KAAK,IAAI,wBAAwB,KAAK,SAAS,KAAK,OAAO,GAC3D;AACA,iBAAK,OAAO;AAAA,cACV,yDAAyD,KAAK,OAAO,YAAY,KAAK,OAAO;AAAA,YAC/F;AACA,uBAAW,CAAC,EAAE,IAAI,KAAK,KAAK,eAAe;AACzC,mBAAK,QAAQ;AAAA,YACf;AACA;AAAA,UACF;AACA,eAAK,OAAO;AAAA,YACV,uDAAuD,KAAK,iBAAiB,IAAI,cAAc,KAAK,cAAc;AAAA,UACpH;AACA,eAAK,kBAAkB;AAAA,QACzB;AAAA,MACF;AAAA,IACF,CAAC;AAED,SAAK,aAAa,MAAM;AACxB,SAAK,yBAAyB;AAAA,EAChC;AAAA,EAEA,MAAc,mBAAkC;AAC9C,SAAK,qBAAqB;AAC1B,SAAK,wBAAwB;AAC7B,UAAM,SAAS,KAAK;AACpB,SAAK,eAAe;AACpB,QAAI,QAAQ;AACV,YAAM,OAAO,KAAK;AAAA,IACpB;AACA,SAAK,YAAY,CAAC;AAElB,QAAI,KAAK,yBAAyB;AAChC,YAAM,KAAK,wBAAwB,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACnD,WAAK,0BAA0B;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,2BAAiC;AACvC,SAAK,wBAAwB;AAC7B,QAAI,KAAK,mBAAmB,EAAG;AAE/B,SAAK,cAAc,KAAK,IAAI;AAC5B,SAAK,oBAAoB,YAAY,MAAM;AACzC,UAAI,CAAC,KAAK,sBAAsB,CAAC,KAAK,QAAQ;AAC5C,aAAK,wBAAwB;AAC7B;AAAA,MACF;AAEA,YAAM,YAAY,KAAK,IAAI,IAAI,KAAK;AACpC,UAAI,YAAY,KAAK,iBAAiB;AACpC,aAAK,OAAO;AAAA,UACV,+DAA+D,YAAY,KAAM,QAAQ,CAAC,CAAC,gBAAgB,KAAK,eAAe,sBAC9G,KAAK,mBAAmB,YAAY,KAAK,iBAAiB,IAAI;AAAA,QACjF;AACA,aAAK,wBAAwB;AAI7B,cAAM,SAAS,KAAK;AACpB,YAAI,QAAQ;AACV,eAAK,qBAAqB;AAC1B,eAAK,eAAe;AACpB,iBAAO,KAAK,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC9B;AAAA,MACF;AAAA,IACF,GAAG,KAAK,IAAI,KAAK,kBAAkB,GAAG,GAAK,CAAC;AAAA,EAC9C;AAAA,EAEQ,0BAAgC;AACtC,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,UAAkB,QAAuB;AAC5D,QAAI,CAAC,KAAK,iBAAiB,IAAI,QAAQ,EAAG;AAC1C,SAAK,iBAAiB,OAAO,QAAQ;AACrC,SAAK,cAAc,OAAO,QAAQ;AAElC,UAAM,YAAY,KAAK,cAAc,IAAI,KAAK,IAAI,IAAI,KAAK,cAAc;AACzE,UAAM,cAAc,aAAa,IAAI,eAAe,YAAY,KAAM,QAAQ,CAAC,CAAC,UAAU;AAC1F,SAAK,OAAO;AAAA,MACV,6CAA6C,QAAQ,WAAW,UAAU,SAAS,eACpE,KAAK,iBAAiB,IAAI,aAAa,KAAK,mBAAmB,YAAY,KAAK,uBAAuB,GAAG,WAAW;AAAA,IACtI;AACA,SAAK,KAAK,sBAAsB,QAAQ;AAExC,QAAI,KAAK,iBAAiB,SAAS,KAAK,CAAC,KAAK,gBAAgB;AAG5D,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,QAAI,KAAK,eAAgB;AACzB,SAAK,OAAO;AAAA,MACV,2DAA2D,KAAK,aAAa;AAAA,IAC/E;AACA,SAAK,iBAAiB,WAAW,YAAY;AAC3C,WAAK,iBAAiB;AACtB,UAAI,KAAK,iBAAiB,SAAS,KAAK,KAAK,oBAAoB;AAC/D,aAAK,OAAO,OAAO,gEAAgE;AACnF,cAAM,KAAK,iBAAiB;AAAA,MAC9B;AAAA,IACF,GAAG,KAAK,aAAa;AAAA,EACvB;AACF;;;ACjiCA,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,SAAAC,cAAa;AACtB,YAAYC,WAAU;AAetB,IAAM,oBAAoB,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AAC9D,IAAM,oBAAoB,OAAO,KAAK,CAAC,GAAM,GAAM,CAAI,CAAC;AAExD,SAAS,eAAe,MAAuB;AAC7C,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,SAAO,KAAK,SAAS,GAAG,CAAC,EAAE,OAAO,iBAAiB,KAAK,KAAK,SAAS,GAAG,CAAC,EAAE,OAAO,iBAAiB;AACtG;AAEA,SAAS,gBAAgB,QAA0B;AAGjD,QAAM,SAA8C,CAAC;AACrD,WAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAC1C,QAAI,OAAO,CAAC,MAAM,KAAQ,OAAO,IAAI,CAAC,MAAM,GAAM;AAChD,UAAI,OAAO,IAAI,CAAC,MAAM,GAAM;AAC1B,eAAO,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,CAAC;AAC9B,aAAK;AAAA,MACP,WAAW,OAAO,IAAI,CAAC,MAAM,KAAQ,OAAO,IAAI,CAAC,MAAM,GAAM;AAC3D,eAAO,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,CAAC;AAC9B,aAAK;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAEjC,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,eAAe,MAAM,MAAM,MAAM;AACvC,UAAM,OAAO,OAAO,IAAI,CAAC;AACzB,UAAM,aAAa,OAAO,KAAK,MAAM,OAAO;AAC5C,QAAI,aAAa,aAAc,KAAI,KAAK,OAAO,SAAS,cAAc,UAAU,CAAC;AAAA,EACnF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,YAAmC;AACtD,MAAI,WAAW,SAAS,EAAG,QAAO;AAClC,QAAM,KAAK,WAAW,CAAC;AACvB,MAAI,OAAO,OAAW,QAAO;AAC7B,SAAO,KAAK;AACd;AAEA,SAAS,yBAAyB,QAAyB;AACzD,QAAM,OAAO,gBAAgB,MAAM;AACnC,aAAW,OAAO,MAAM;AACtB,UAAM,IAAI,YAAY,GAAG;AACzB,QAAI,MAAM,EAAG,QAAO;AAAA,EACtB;AACA,SAAO;AACT;AAQO,IAAM,2BAAN,cAAuCF,cAI3C;AAAA,EACO;AAAA,EACA;AAAA,EACA;AAAA,EAAuB;AAAA,EAAyB;AAAA,EAChD;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,UAAU,oBAAI,IAAyB;AAAA,EACvC;AAAA,EAIA,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,YAA2B;AAAA;AAAA,EAC3B,YAA2B;AAAA;AAAA,EAEnC,YAAY,SAA0C;AACpD,UAAM;AACN,SAAK,cAAc,QAAQ;AAC3B,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,SAAS,QAAQ,UAAU;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,SAAK,OAAO,KAAK,8DAA8D;AAE/E,UAAM,KAAK,YAAY,MAAM;AAC7B,SAAK,OAAO,KAAK,0DAA0D;AAG3E,SAAK,aAAkB,mBAAa,CAAC,KAAK,QAAQ;AAChD,UAAI,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,GAAG,KAAK,IAAI,OAAO;AAC1D,aAAK,OAAO,KAAK,oDAAoD,IAAI,OAAO,aAAa,EAAE;AAC/F,aAAK,QAAQ,IAAI,GAAG;AACpB,aAAK,KAAK,UAAU,IAAI,OAAO,iBAAiB,SAAS;AAGzD,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,cAAc;AAAA,UACd,+BAA+B;AAAA,QACjC,CAAC;AAGD,YAAI,GAAG,SAAS,MAAM;AACpB,eAAK,QAAQ,OAAO,GAAG;AACvB,eAAK,OAAO,KAAK,gDAAgD;AAAA,QACnE,CAAC;AAAA,MACH,OAAO;AACL,YAAI,UAAU,GAAG;AACjB,YAAI,IAAI,WAAW;AAAA,MACrB;AAAA,IACF,CAAC;AAGD,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAK,WAAY,OAAO,KAAK,YAAY,aAAa,MAAM;AAC1D,aAAK,OAAO,KAAK,4DAA4D,KAAK,UAAU,EAAE;AAC9F,gBAAQ;AAAA,MACV,CAAC;AACD,WAAK,WAAY,GAAG,SAAS,MAAM;AAAA,IACrC,CAAC;AAGD,SAAK,OAAO,KAAK,+EAA+E;AAEhG,UAAM,SAASC,OAAM,UAAU;AAAA,MAC7B;AAAA;AAAA;AAAA,MAGA;AAAA,MAAa;AAAA;AAAA,MAEb;AAAA,MAAM,OAAO,KAAK,QAAQ;AAAA,MAC1B;AAAA,MAAW;AAAA,MACX;AAAA,MAAgC;AAAA,MAChC;AAAA,MAAM;AAAA;AAAA,MACN;AAAA,MAAM;AAAA;AAAA,MACN;AAAA,MAAQ;AAAA;AAAA,MACR;AAAA,MAAe;AAAA,MACf;AAAA,MAAa;AAAA,MACb;AAAA,MAAM;AAAA;AAAA,MACN;AAAA;AAAA,IACF,GAAG;AAAA,MACD,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,SAAK,gBAAgB;AACrB,SAAK,OAAO,KAAK,2DAA2D,OAAO,GAAG,GAAG;AAGzF,QAAI,aAAa;AACjB,UAAM,gBAAgB,CAAC,cAAsB;AAG3C,UAAI,CAAC,eAAe,SAAS,GAAG;AAC9B;AAAA,MACF;AACA;AACA,UAAI,eAAe,GAAG;AACpB,aAAK,OAAO,KAAK,0DAA0D,UAAU,MAAM,SAAS;AAAA,MACtG;AACA,UAAI,OAAO,SAAS,CAAC,OAAO,MAAM,WAAW;AAC3C,YAAI;AACF,iBAAO,MAAM,MAAM,SAAS;AAAA,QAC9B,SAAS,OAAO;AACd,eAAK,OAAO,MAAM,mDAAmD,KAAK,EAAE;AAC5E,eAAK,KAAK,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,QAC9E;AAAA,MACF;AAAA,IACF;AAGA,SAAK,eAAe;AACpB,SAAK,kBAAkB;AACvB,SAAK,YAAY;AACjB,SAAK,YAAY;AAEjB,SAAK,gBAAgB,CAAC,SAAc;AAClC,YAAM,OAAe,OAAO,SAAS,IAAI,IAAI,OAAO,MAAM;AAC1D,YAAM,aAAsB,OAAO,SAAS,IAAI,IAAI,yBAAyB,IAAI,IAAI,QAAQ,MAAM,UAAU;AAC7G,UAAI,CAAC,OAAO,SAAS,IAAI,EAAG;AAI5B,UAAI,CAAC,OAAO,SAAS,IAAI,GAAG;AAC1B,aAAK,kBAAkB;AAAA,MACzB,WAAW,KAAK,iBAAiB;AAC/B;AAAA,MACF;AAGA,YAAM,OAAO,gBAAgB,IAAI;AACjC,iBAAW,OAAO,MAAM;AACtB,cAAM,IAAI,YAAY,GAAG;AACzB,YAAI,MAAM,EAAG,MAAK,YAAY;AAC9B,YAAI,MAAM,EAAG,MAAK,YAAY;AAAA,MAChC;AAGA,UAAI,CAAC,KAAK,cAAc;AACtB,YAAI,CAAC,WAAY;AACjB,aAAK,eAAe;AACpB,aAAK,OAAO,KAAK,0EAA0E;AAAA,MAC7F;AAGA,UAAI,cAAc,KAAK,aAAa,KAAK,WAAW;AAElD,YAAI,SAAS;AACb,YAAI,SAAS;AACb,mBAAW,OAAO,MAAM;AACtB,gBAAM,IAAI,YAAY,GAAG;AACzB,cAAI,MAAM,EAAG,UAAS;AACtB,cAAI,MAAM,EAAG,UAAS;AAAA,QACxB;AACA,YAAI,CAAC,UAAU,CAAC,QAAQ;AACtB,gBAAM,UAAU,OAAO,OAAO;AAAA,YAC5B;AAAA,YAAmB,KAAK;AAAA,YACxB;AAAA,YAAmB,KAAK;AAAA,YACxB;AAAA,UACF,CAAC;AACD,wBAAc,OAAO;AACrB;AAAA,QACF;AAAA,MACF;AAEA,oBAAc,IAAI;AAAA,IACpB;AAGA,SAAK,YAAY,GAAG,mBAA0B,KAAK,aAAoB;AACvE,SAAK,YAAY,GAAG,cAAc,KAAK,aAAoB;AAG3D,WAAO,OAAO,GAAG,QAAQ,CAAC,SAAiB;AAEzC,iBAAW,UAAU,KAAK,SAAS;AACjC,YAAI,CAAC,OAAO,WAAW;AACrB,cAAI;AACF,mBAAO,MAAM,IAAI;AAAA,UACnB,SAAS,OAAO;AAEd,iBAAK,QAAQ,OAAO,MAAM;AAAA,UAC5B;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAGD,QAAI,eAAe;AACnB,WAAO,OAAO,GAAG,QAAQ,CAAC,SAAS;AACjC,YAAM,SAAS,KAAK,SAAS;AAC7B,sBAAgB;AAIhB,YAAM,kBACJ,OAAO,SAAS,uBAAuB,KACvC,OAAO,SAAS,sBAAsB,KACtC,OAAO,SAAS,2BAA2B,KAC3C,OAAO,SAAS,UAAU,KAC1B,OAAO,SAAS,YAAY,KAC5B,OAAO,SAAS,wBAAwB,KACxC,OAAO,SAAS,0BAA0B;AAE5C,UAAI,iBAAiB;AAEnB,aAAK,OAAO,KAAK,qDAAqD,OAAO,KAAK,CAAC,EAAE;AACrF;AAAA,MACF;AAGA,YAAM,kBACJ,OAAO,SAAS,oBAAoB,KACpC,OAAO,SAAS,eAAe,KAC/B,OAAO,SAAS,wBAAwB,KACxC,OAAO,SAAS,aAAa,KAC7B,OAAO,SAAS,oBAAoB,KACpC,OAAO,SAAS,gBAAgB,KAChC,OAAO,SAAS,mBAAmB;AAErC,UAAI,iBAAiB;AACnB,aAAK,OAAO,MAAM,qDAAqD,OAAO,KAAK,CAAC,EAAE;AAEtF,aAAK,KAAK,SAAS,IAAI,MAAM,iBAAiB,MAAM,EAAE,CAAC;AAAA,MACzD,OAAO;AACL,aAAK,OAAO,KAAK,6CAA6C,OAAO,KAAK,CAAC,EAAE;AAAA,MAC/E;AAAA,IACF,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,SAAS;AAC3B,UAAI,SAAS,GAAG;AACd,aAAK,OAAO,MAAM,sDAAsD,IAAI,EAAE;AAC9E,aAAK,KAAK,SAAS,IAAI,MAAM,2BAA2B,IAAI,EAAE,CAAC;AAAA,MACjE;AACA,WAAK,SAAS;AACd,WAAK,KAAK,OAAO;AAAA,IACnB,CAAC;AAED,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACrB,WAAO,oBAAoB,KAAK,UAAU,GAAG,KAAK,IAAI;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAK1B,eAAW,UAAU,KAAK,SAAS;AACjC,UAAI,CAAC,OAAO,WAAW;AACrB,eAAO,IAAI;AAAA,MACb;AAAA,IACF;AACA,SAAK,QAAQ,MAAM;AAGnB,QAAI;AACF,YAAM,KAAK,YAAY,KAAK;AAAA,IAC9B,QAAQ;AAAA,IAER;AAGA,QAAI,KAAK,eAAe;AACtB,WAAK,YAAY,eAAe,mBAA0B,KAAK,aAAoB;AACnF,WAAK,YAAY,eAAe,cAAc,KAAK,aAAoB;AAAA,IACzE;AACA,SAAK,gBAAgB;AAGrB,QAAI,KAAK,eAAe;AACtB,YAAM,OAAO,KAAK;AAClB,UAAI;AACF,aAAK,KAAK,SAAS;AAAA,MACrB,QAAQ;AAAA,MAER;AAEA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,IAAI,WAAW,MAAM;AACzB,cAAI;AACF,iBAAK,KAAK,SAAS;AAAA,UACrB,QAAQ;AAAA,UAER;AACA,kBAAQ;AAAA,QACV,GAAG,IAAI;AAEP,QAAC,GAAW,QAAQ;AACpB,aAAK,KAAK,SAAS,MAAM;AACvB,uBAAa,CAAC;AACd,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AACA,SAAK,gBAAgB;AAGrB,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI,QAAc,CAAC,YAAY;AAEnC,QAAC,KAAK,YAAoB,sBAAsB;AAChD,QAAC,KAAK,YAAoB,uBAAuB;AACjD,aAAK,WAAY,MAAM,MAAM,QAAQ,CAAC;AAAA,MACxC,CAAC;AACD,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AACF;;;AC/ZA,SAAS,gBAAAE,qBAAoB;AAC7B,YAAYC,WAAU;;;ACDtB,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,SAAAC,cAAkD;AA8B3D,IAAM,WAAW,OAAO,KAAK,CAAC,KAAM,GAAI,CAAC;AACzC,IAAM,WAAW,OAAO,KAAK,CAAC,KAAM,GAAI,CAAC;AAUlC,IAAM,mBAAN,cAA+BD,cAAa;AAAA,EAChC;AAAA,EAIT,SAAgD;AAAA,EAChD,UAAU;AAAA,EACV,SAAS;AAAA,EACT,aAAa,OAAO,MAAM,CAAC;AAAA,EAC3B,aAAa;AAAA,EACb,gBAAgB;AAAA,EAExB,YAAY,SAAkC;AAC5C,UAAM;AACN,SAAK,UAAU;AAAA,MACb,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ,WAAW;AAAA,MAC5B,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,WAAW,KAAK,OAAQ;AACjC,SAAK,UAAU;AAEf,UAAM,EAAE,OAAO,SAAS,OAAO,QAAQ,OAAO,IAAI,KAAK;AAGvD,UAAM,OAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA,UAAU,SAAS,SAAS;AAAA,MAC5B;AAAA,MACA;AAAA,IACF;AAGA,UAAM,UAAoB,CAAC;AAG3B,QAAI,SAAS,QAAQ;AACnB,YAAM,IAAI,SAAS;AACnB,YAAM,IAAI,UAAU;AACpB,cAAQ,KAAK,SAAS,CAAC,IAAI,CAAC,EAAE;AAAA,IAChC;AAGA,QAAI,QAAQ;AACV,cAAQ,KAAK,OAAO,MAAM,EAAE;AAAA,IAC9B;AAEA,QAAI,QAAQ,SAAS,GAAG;AACtB,WAAK,KAAK,OAAO,QAAQ,KAAK,GAAG,CAAC;AAAA,IACpC;AAGA,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,OAAO;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,SAAK,IAAI,SAAS,8BAA8B,KAAK,KAAK,GAAG,CAAC,EAAE;AAEhE,SAAK,SAASC,OAAM,UAAU,MAAM;AAAA,MAClC,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,SAAK,OAAO,OAAO,GAAG,QAAQ,CAAC,SAAiB;AAC9C,WAAK,eAAe,IAAI;AAAA,IAC1B,CAAC;AAED,SAAK,OAAO,OAAO,GAAG,QAAQ,CAAC,SAAiB;AAC9C,YAAM,MAAM,KAAK,SAAS,EAAE,KAAK;AACjC,UAAI,KAAK;AACP,aAAK,IAAI,SAAS,WAAW,GAAG,EAAE;AAAA,MACpC;AAAA,IACF,CAAC;AAED,SAAK,OAAO,GAAG,SAAS,CAAC,SAAS;AAChC,WAAK,IAAI,SAAS,2BAA2B,IAAI,EAAE;AACnD,WAAK,SAAS;AACd,UAAI,CAAC,KAAK,QAAQ;AAChB,aAAK,KAAK,SAAS,IAAI;AAAA,MACzB;AAAA,IACF,CAAC;AAED,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,IAAI,SAAS,iBAAiB,IAAI,OAAO,EAAE;AAChD,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,YAAoB,WAA0B;AACjD,QAAI,CAAC,KAAK,WAAW,KAAK,UAAU,CAAC,KAAK,QAAQ;AAChD;AAAA,IACF;AAEA,SAAK,gBAAgB,aAAa,KAAK,IAAI,IAAI;AAE/C,QAAI;AACF,WAAK,OAAO,MAAM,MAAM,UAAU;AAAA,IACpC,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,8BAA8B,GAAG,EAAE;AAAA,IACvD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,eAAe,MAAoB;AACzC,SAAK,aAAa,OAAO,OAAO,CAAC,KAAK,YAAY,IAAI,CAAC;AAGvD,WAAO,MAAM;AAEX,YAAM,WAAW,KAAK,WAAW,QAAQ,QAAQ;AACjD,UAAI,WAAW,GAAG;AAEhB,aAAK,aAAa,OAAO,MAAM,CAAC;AAChC;AAAA,MACF;AAGA,UAAI,WAAW,GAAG;AAChB,aAAK,aAAa,KAAK,WAAW,SAAS,QAAQ;AAAA,MACrD;AAGA,YAAM,WAAW,KAAK,WAAW,QAAQ,UAAU,CAAC;AACpD,UAAI,WAAW,GAAG;AAEhB;AAAA,MACF;AAGA,YAAM,WAAW,WAAW;AAC5B,YAAM,YAAY,KAAK,WAAW,SAAS,GAAG,QAAQ;AAGtD,WAAK,aAAa,KAAK,WAAW,SAAS,QAAQ;AAGnD,WAAK;AACL,YAAM,QAAoB;AAAA,QACxB,MAAM;AAAA,QACN,WAAW,KAAK;AAAA,MAClB;AACA,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AAEd,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,aAAK,OAAO,MAAM,IAAI;AAAA,MACxB,QAAQ;AAAA,MAER;AAGA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,KAAK,KAAK;AAChB,YAAI,CAAC,IAAI;AACP,kBAAQ;AACR;AAAA,QACF;AAEA,cAAM,UAAU,WAAW,MAAM;AAC/B,aAAG,KAAK,SAAS;AACjB,kBAAQ;AAAA,QACV,GAAG,GAAI;AAEP,WAAG,KAAK,SAAS,MAAM;AACrB,uBAAa,OAAO;AACpB,kBAAQ;AAAA,QACV,CAAC;AAED,YAAI;AACF,aAAG,KAAK,SAAS;AAAA,QACnB,QAAQ;AACN,uBAAa,OAAO;AACpB,kBAAQ;AAAA,QACV;AAAA,MACF,CAAC;AAED,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,KAAK,SAAS,CAAC;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,KAAK,WAAW,CAAC,KAAK,UAAU,KAAK,WAAW;AAAA,EACzD;AAAA,EAEQ,IACN,OACA,SACM;AACN,SAAK,QAAQ,SAAS,OAAO,sBAAsB,OAAO,EAAE;AAAA,EAC9D;AACF;AAmBO,SAAS,sBAA8B;AAC5C,SAAO,gBAAgB,KAAK,IAAI,CAAC;AACnC;AAEO,SAAS,oBAAoB,UAA0B;AAC5D,SAAO,uCAAuC,QAAQ;AACxD;AAEO,SAAS,iBAAiB,OAAe,UAA0B;AACxE,QAAM,SAAS,OAAO;AAAA,IACpB,KAAK,QAAQ;AAAA;AAAA,kBAAmD,MAAM,MAAM;AAAA;AAAA;AAAA,EAC9E;AACA,SAAO,OAAO,OAAO,CAAC,QAAQ,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC;AAC3D;;;ADtPO,IAAM,sBAAN,cAAkCC,cAAa;AAAA,EACnC;AAAA,EACA,UAAU,oBAAI,IAAyB;AAAA,EAChD,aAAiC;AAAA,EACjC,cAAuC;AAAA,EACvC,eAA0D;AAAA,EAC1D,aAAmC;AAAA,EACnC,gBAAwC;AAAA,EACxC,UAAU;AAAA,EACV,kBAAkB;AAAA,EAE1B,YAAY,SAAqC;AAC/C,UAAM;AACN,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AAEf,UAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,UAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,UAAMC,QAAO,KAAK,QAAQ,QAAQ;AAElC,SAAK,aAAkB,mBAAa,CAAC,KAAK,QAAQ;AAChD,WAAK,cAAc,KAAK,KAAKA,KAAI;AAAA,IACnC,CAAC;AAED,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAK,WAAY,GAAG,SAAS,CAAC,QAAQ;AACpC,aAAK,IAAI,SAAS,sBAAsB,IAAI,OAAO,EAAE;AACrD,eAAO,GAAG;AAAA,MACZ,CAAC;AAED,WAAK,WAAY,OAAO,MAAM,MAAM,MAAM;AACxC,aAAK;AAAA,UACH;AAAA,UACA,kCAAkC,IAAI,IAAI,IAAI,GAAGA,KAAI;AAAA,QACvD;AACA,aAAK,KAAK,WAAW,EAAE,MAAM,MAAM,MAAAA,MAAK,CAAC;AACzC,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,UAAU;AAGf,eAAW,CAAC,IAAI,MAAM,KAAK,KAAK,SAAS;AACvC,UAAI;AACF,eAAO,SAAS,IAAI;AAAA,MACtB,QAAQ;AAAA,MAER;AACA,WAAK,QAAQ,OAAO,EAAE;AAAA,IACxB;AAGA,UAAM,KAAK,WAAW;AAGtB,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAK,WAAY,MAAM,MAAM,QAAQ,CAAC;AAAA,MACxC,CAAC;AACD,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,IAAI,QAAQ,sBAAsB;AACvC,SAAK,KAAK,SAAS;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKQ,cACN,KACA,KACA,cACM;AACN,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AAEhE,QAAI,IAAI,aAAa,cAAc;AACjC,UAAI,aAAa;AACjB,UAAI,IAAI,WAAW;AACnB;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,OAAO;AACxB,UAAI,aAAa;AACjB,UAAI,IAAI,oBAAoB;AAC5B;AAAA,IACF;AAEA,SAAK,kBAAkB,KAAK,GAAG;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKQ,kBACN,KACA,KACM;AACN,UAAM,WAAW,UAAU,EAAE,KAAK,eAAe;AACjD,UAAM,WAAW,oBAAoB;AAErC,UAAM,SAAsB;AAAA,MAC1B,IAAI;AAAA,MACJ,UAAU;AAAA,MACV;AAAA,MACA,aAAa,KAAK,IAAI;AAAA,IACxB;AAEA,SAAK,QAAQ,IAAI,UAAU,MAAM;AACjC,SAAK;AAAA,MACH;AAAA,MACA,2BAA2B,QAAQ,YAAY,KAAK,QAAQ,IAAI;AAAA,IAClE;AACA,SAAK,KAAK,oBAAoB,EAAE,IAAI,UAAU,OAAO,KAAK,QAAQ,KAAK,CAAC;AAGxE,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB,oBAAoB,QAAQ;AAAA,MAC5C,iBAAiB;AAAA,MACjB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY;AAAA,IACd,CAAC;AAGD,UAAM,UAAU,MAAM;AACpB,WAAK,QAAQ,OAAO,QAAQ;AAC5B,WAAK;AAAA,QACH;AAAA,QACA,8BAA8B,QAAQ,gBAAgB,KAAK,QAAQ,IAAI;AAAA,MACzE;AACA,WAAK,KAAK,uBAAuB;AAAA,QAC/B,IAAI;AAAA,QACJ,OAAO,KAAK,QAAQ;AAAA,MACtB,CAAC;AAGD,UAAI,KAAK,QAAQ,SAAS,GAAG;AAC3B,aAAK,WAAW;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AAGvB,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAA6B;AACzC,QAAI,KAAK,YAAa;AAEtB,SAAK,IAAI,QAAQ,iCAAiC;AAElD,UAAM,EAAE,KAAK,SAAS,SAAS,SAAS,SAAS,OAAO,QAAQ,OAAO,IACrE,KAAK;AAEP,QAAI;AAGF,WAAK,eAAe;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,EAAE,QAAQ,IAAI;AAAA,MAC1B;AAGA,WAAK,aAAa,KAAK,WAAW;AAAA,IACpC,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,2BAA2B,GAAG,EAAE;AAClD,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAA4B;AACxC,QAAI,CAAC,KAAK,aAAc;AAExB,QAAI,cAAwB,CAAC;AAC7B,QAAI,qBAAqB;AAEzB,QAAI;AACF,uBAAiB,SAAS,KAAK,cAAc;AAC3C,YAAI,CAAC,KAAK,WAAW,KAAK,QAAQ,SAAS,EAAG;AAG9C,cAAM,EAAE,MAAM,MAAM,cAAc,UAAU,IAAI;AAEhD,YAAI,SAAS,YAAY,SAAS,SAAU;AAC5C,YAAI,CAAC,QAAQ,KAAK,WAAW,EAAG;AAGhC,YAAI;AACJ,YAAI,cAAc,QAAQ;AACxB,mBAASC,iBAAoB,IAAI;AACjC,cAAI,CAAC,KAAK,eAAe;AACvB,iBAAK,gBAAgB;AACrB,iBAAK,gBAAgB;AAAA,UACvB;AAAA,QACF,OAAO;AACL,mBAAS,gBAAoB,IAAI;AACjC,cAAI,CAAC,KAAK,eAAe;AACvB,iBAAK,gBAAgB;AACrB,iBAAK,gBAAgB;AAAA,UACvB;AAAA,QACF;AAGA,YAAI,oBAAoB;AACtB,cAAI,SAAS,UAAU;AACrB,iCAAqB;AAAA,UACvB,OAAO;AACL;AAAA,UACF;AAAA,QACF;AAGA,YAAI,KAAK,aAAa;AACpB,eAAK,YAAY,KAAK,QAAQ,YAAY;AAAA,QAC5C;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,KAAK,SAAS;AAChB,aAAK,IAAI,SAAS,iBAAiB,GAAG,EAAE;AACxC,aAAK,KAAK,SAAS,GAAG;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAwB;AAC9B,QAAI,KAAK,eAAe,CAAC,KAAK,cAAe;AAE7C,UAAM,EAAE,SAAS,OAAO,QAAQ,OAAO,IAAI,KAAK;AAEhD,SAAK,cAAc,IAAI,iBAAiB;AAAA,MACtC,OAAO,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,KAAK,QAAQ;AAAA,IACvB,CAAC;AAED,SAAK,YAAY,GAAG,SAAS,CAAC,UAAsB;AAClD,WAAK,eAAe,KAAK;AAAA,IAC3B,CAAC;AAED,SAAK,YAAY,GAAG,SAAS,CAAC,QAAQ;AACpC,WAAK,IAAI,SAAS,sBAAsB,GAAG,EAAE;AAAA,IAC/C,CAAC;AAED,SAAK,YAAY,GAAG,SAAS,MAAM;AACjC,WAAK,IAAI,SAAS,oBAAoB;AAAA,IACxC,CAAC;AAED,SAAK,YAAY,MAAM;AACvB,SAAK;AAAA,MACH;AAAA,MACA,qCAAqC,KAAK,aAAa;AAAA,IACzD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,OAAyB;AAC9C,eAAW,UAAU,KAAK,QAAQ,OAAO,GAAG;AAC1C,UAAI;AACF,cAAM,YAAY,iBAAiB,MAAM,MAAM,OAAO,QAAQ;AAC9D,eAAO,SAAS,MAAM,SAAS;AAAA,MACjC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAA4B;AAExC,QAAI,KAAK,aAAa;AACpB,YAAM,KAAK,YAAY,KAAK;AAC5B,WAAK,cAAc;AAAA,IACrB;AAGA,QAAI,KAAK,cAAc;AACrB,UAAI;AACF,cAAM,KAAK,aAAa,OAAO,MAAgB;AAAA,MACjD,QAAQ;AAAA,MAER;AACA,WAAK,eAAe;AAAA,IACtB;AAGA,QAAI,KAAK,YAAY;AACnB,UAAI;AACF,cAAM,KAAK;AAAA,MACb,QAAQ;AAAA,MAER;AACA,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,gBAAgB;AAErB,SAAK,IAAI,SAAS,gBAAgB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAyB;AACvB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,YAKE;AACA,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,SAAS,KAAK,QAAQ;AAAA,MACtB,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK,aAAa,cAAc,KAAK;AAAA,IAC/C;AAAA,EACF;AAAA,EAEQ,IACN,OACA,SACM;AACN,SAAK,QAAQ,SAAS,OAAO,yBAAyB,OAAO,EAAE;AAAA,EACjE;AACF;;;AErZA,SAAS,gBAAAC,qBAAoB;AAiH7B,SAAS,oBAAoB,QAA0B;AACrD,QAAM,WAAqB,CAAC;AAC5B,MAAI,SAAS;AAEb,SAAO,SAAS,OAAO,QAAQ;AAE7B,QAAI,eAAe;AACnB,QACE,SAAS,KAAK,OAAO,UACrB,OAAO,MAAM,MAAM,KACnB,OAAO,SAAS,CAAC,MAAM,KACvB,OAAO,SAAS,CAAC,MAAM,KACvB,OAAO,SAAS,CAAC,MAAM,GACvB;AACA,qBAAe;AAAA,IACjB,WACE,SAAS,KAAK,OAAO,UACrB,OAAO,MAAM,MAAM,KACnB,OAAO,SAAS,CAAC,MAAM,KACvB,OAAO,SAAS,CAAC,MAAM,GACvB;AACA,qBAAe;AAAA,IACjB,OAAO;AACL;AACA;AAAA,IACF;AAEA,UAAM,YAAY,SAAS;AAG3B,QAAI,UAAU,OAAO;AACrB,aAAS,IAAI,WAAW,IAAI,OAAO,SAAS,GAAG,KAAK;AAClD,UACE,OAAO,CAAC,MAAM,KACd,OAAO,IAAI,CAAC,MAAM,MACjB,OAAO,IAAI,CAAC,MAAM,KAChB,IAAI,IAAI,OAAO,UAAU,OAAO,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,MAAM,IACrE;AACA,kBAAU;AACV;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,WAAW;AACvB,eAAS,KAAK,OAAO,SAAS,WAAW,OAAO,CAAC;AAAA,IACnD;AAEA,aAAS;AAAA,EACX;AAEA,SAAO;AACT;AAKA,SAAS,eAAe,SAAyB;AAC/C,SAAO,QAAQ,CAAC,IAAK;AACvB;AAKA,SAASC,gBAAe,SAAyB;AAC/C,SAAQ,QAAQ,CAAC,KAAM,IAAK;AAC9B;AAiBO,IAAM,uBAAN,cAAmCC,cAAa;AAAA,EACpC;AAAA,EACA,WAAW,oBAAI,IAA2B;AAAA,EACnD,mBAAmB;AAAA,EACnB,eAAoB;AAAA,EAE5B,YAAY,SAAsC;AAChD,UAAM;AACN,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAA2B;AACvC,QAAI,KAAK,aAAc,QAAO,KAAK;AAEnC,QAAI;AACF,WAAK,eAAe,MAAM,OAAO,QAAQ;AACzC,aAAO,KAAK;AAAA,IACd,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR;AAAA,SAAsF,GAAG;AAAA,MAC3F;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAoE;AACxE,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,UAAM,EAAE,mBAAmB,kBAAkB,sBAAsB,IACjE;AAEF,UAAM,YAAY,UAAU,EAAE,KAAK,gBAAgB,IAAI,KAAK,IAAI,CAAC;AAEjE,SAAK,IAAI,QAAQ,2BAA2B,SAAS,EAAE;AAGvD,UAAM,aAAoB,CAAC;AAG3B,UAAM,cAAc,KAAK,QAAQ,eAAe;AAAA,MAC9C;AAAA,IACF;AACA,eAAW,QAAQ,aAAa;AAC9B,iBAAW,KAAK,EAAE,KAAK,CAAC;AAAA,IAC1B;AAGA,QAAI,KAAK,QAAQ,aAAa;AAC5B,iBAAW,KAAK,GAAG,KAAK,QAAQ,WAAW;AAAA,IAC7C;AAGA,UAAM,iBAAiB,IAAI,kBAAkB;AAAA,MAC3C;AAAA,MACA,cAAc,KAAK,QAAQ;AAAA,MAC3B,4BAA4B,KAAK,QAAQ;AAAA,MACzC,oBAAoB,KAAK,QAAQ;AAAA,MACjC,QAAQ;AAAA,QACN,OAAO;AAAA,UACL,IAAI,sBAAsB;AAAA,YACxB,UAAU;AAAA,YACV,WAAW;AAAA,YACX,cAAc;AAAA,cACZ,EAAE,MAAM,OAAO;AAAA,cACf,EAAE,MAAM,QAAQ,WAAW,MAAM;AAAA,cACjC,EAAE,MAAM,YAAY;AAAA,YACtB;AAAA,YACA,YACE;AAAA,UACJ,CAAC;AAAA,QACH;AAAA,QACA,OAAO;AAAA,UACL,IAAI,sBAAsB;AAAA,YACxB,UAAU;AAAA,YACV,WAAW;AAAA,YACX,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM,UAAyB;AAAA,MAC7B,IAAI;AAAA,MACJ;AAAA,MACA,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,cAAc;AAAA,MACd,UAAU;AAAA,MACV,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,WAAW,oBAAI,KAAK;AAAA,MACpB,OAAO;AAAA,MACP,OAAO;AAAA,QACL,aAAa;AAAA,QACb,aAAa;AAAA,QACb,WAAW;AAAA,QACX,mBAAmB;AAAA,MACrB;AAAA,IACF;AAEA,SAAK,SAAS,IAAI,WAAW,OAAO;AAIpC,UAAM,YAAa,KAAK,OAAO,IAAI,eAAgB;AACnD,UAAM,aAAa,IAAI,iBAAiB,EAAE,MAAM,SAAS,MAAM,UAAU,CAAC;AAC1E,UAAM,cAAc,eAAe,SAAS,UAAU;AACtD,YAAQ,aAAa;AAGrB,SAAK;AAAA,MACH;AAAA,MACA,6BAA6B,WAAW,IAAI,mBAAmB,KAAK,UAAU,aAAa,gBAAgB,KAAK,CAAC,CAAC,CAAC;AAAA,IACrH;AAGA,UAAM,YAAa,KAAK,OAAO,IAAI,eAAgB;AACnD,UAAM,aAAa,IAAI,iBAAiB,EAAE,MAAM,SAAS,MAAM,UAAU,CAAC;AAC1E,mBAAe,SAAS,UAAU;AAClC,YAAQ,aAAa;AAGrB,UAAM,mBAAmB,eAAe,kBAAkB,SAAS;AAAA,MACjE,SAAS;AAAA,MACT,gBAAgB;AAAA;AAAA,IAClB,CAAC;AACD,YAAQ,mBAAmB;AAE3B,qBAAiB,SAAS,MAAM;AAC9B,WAAK,IAAI,QAAQ,yCAAyC,SAAS,EAAE;AAAA,IACvE;AAGA,QAAI,KAAK,QAAQ,gBAAgB;AAC/B,YAAM,cAAc,eAAe,kBAAkB,YAAY;AAAA,QAC/D,SAAS;AAAA,MACX,CAAC;AACD,cAAQ,cAAc;AAEtB,kBAAY,SAAS,MAAM;AACzB,aAAK;AAAA,UACH;AAAA,UACA,4CAA4C,SAAS;AAAA,QACvD;AACA,aAAK,KAAK,oBAAoB,EAAE,UAAU,CAAC;AAAA,MAC7C;AAEA,kBAAY,YAAY,OAAO,UAAe;AAE5C,YAAI,QAAQ,YAAY,MAAM,gBAAgB,aAAa;AACzD,cAAI;AACF,kBAAM,YAAY,OAAO,KAAK,MAAM,IAAI;AACxC,kBAAM,QAAQ,SAAS,UAAU,SAAS;AAC1C,oBAAQ,MAAM,qBAAqB,UAAU;AAAA,UAC/C,SAAS,KAAK;AACZ,iBAAK,IAAI,SAAS,kCAAkC,GAAG,EAAE;AAAA,UAC3D;AAAA,QACF;AAAA,MACF;AAEA,kBAAY,UAAU,MAAM;AAC1B,aAAK;AAAA,UACH;AAAA,UACA,4CAA4C,SAAS;AAAA,QACvD;AACA,aAAK,KAAK,oBAAoB,EAAE,UAAU,CAAC;AAAA,MAC7C;AAAA,IACF;AAGA,mBAAe,yBAAyB,UAAU,CAAC,UAAkB;AACnE,WAAK,IAAI,QAAQ,4BAA4B,SAAS,KAAK,KAAK,EAAE;AAClE,UAAI,UAAU,aAAa;AACzB,gBAAQ,QAAQ;AAChB,aAAK,KAAK,qBAAqB,EAAE,UAAU,CAAC;AAAA,MAC9C,WAAW,UAAU,UAAU;AAC7B,gBAAQ,QAAQ;AAChB,aAAK,aAAa,SAAS,EAAE,MAAM,CAAC,QAAQ;AAC1C,eAAK,IAAI,SAAS,gCAAgC,KAAK,KAAK,GAAG,EAAE;AAAA,QACnE,CAAC;AAAA,MACH;AAAA,IAEF,CAAC;AAGD,mBAAe,sBAAsB,UAAU,CAAC,UAAkB;AAChE,WAAK,IAAI,SAAS,wBAAwB,SAAS,KAAK,KAAK,EAAE;AAC/D,UAAI,UAAU,YAAY,UAAU,UAAU;AAC5C,aAAK,aAAa,SAAS,EAAE,MAAM,CAAC,QAAQ;AAC1C,eAAK;AAAA,YACH;AAAA,YACA,uCAAuC,KAAK,KAAK,GAAG;AAAA,UACtD;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAGD,UAAM,QAAQ,MAAM,eAAe,YAAY;AAC/C,UAAM,eAAe,oBAAoB,KAAK;AAG9C,UAAM,KAAK,oBAAoB,gBAAgB,GAAI;AAEnD,UAAM,mBAAmB,eAAe;AACxC,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AAEA,SAAK,KAAK,mBAAmB,EAAE,UAAU,CAAC;AAE1C,WAAO;AAAA,MACL;AAAA,MACA,OAAO;AAAA,QACL,KAAK,iBAAiB;AAAA,QACtB,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,WAAmB,QAAqC;AACzE,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,WAAW,SAAS,YAAY;AAAA,IAClD;AAEA,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,UAAM,EAAE,sBAAsB,IAAI;AAElC,SAAK,IAAI,QAAQ,sCAAsC,SAAS,EAAE;AAGlE,UAAM,QAAQ,eAAe;AAAA,MAC3B,IAAI,sBAAsB,OAAO,KAAK,OAAO,IAAI;AAAA,IACnD;AAGA,UAAM,KAAK,kBAAkB,OAAO;AAGpC,QAAI,KAAK,QAAQ,kBAAkB,QAAQ,aAAa;AACtD,YAAM,KAAK,cAAc,OAAO;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBACJ,WACA,WACe;AACf,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,WAAW,SAAS,YAAY;AAAA,IAClD;AAEA,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,UAAM,EAAE,gBAAgB,IAAI;AAE5B,UAAM,QAAQ,eAAe;AAAA,MAC3B,IAAI,gBAAgB,UAAU,WAAW,UAAU,UAAU,GAAG;AAAA,IAClE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,WAAkC;AACnD,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,QAAS;AAEd,SAAK,IAAI,QAAQ,0BAA0B,SAAS,EAAE;AACtD,YAAQ,QAAQ;AAGhB,QAAI,QAAQ,UAAU;AACpB,UAAI;AACF,cAAM,QAAQ,SAAS,KAAK;AAAA,MAC9B,SAAS,KAAK;AACZ,aAAK,IAAI,SAAS,4BAA4B,GAAG,EAAE;AAAA,MACrD;AACA,cAAQ,WAAW;AAAA,IACrB;AAGA,QAAI,QAAQ,aAAa;AACvB,UAAI;AACF,gBAAQ,YAAY,MAAM;AAAA,MAC5B,SAAS,KAAK;AACZ,aAAK,IAAI,SAAS,+BAA+B,GAAG,EAAE;AAAA,MACxD;AACA,cAAQ,cAAc;AAAA,IACxB;AAGA,QAAI,QAAQ,SAAS;AACnB,cAAQ,QAAQ;AAAA,IAClB;AAGA,QAAI;AACF,YAAM,QAAQ,eAAe,MAAM;AAAA,IACrC,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,kCAAkC,GAAG,EAAE;AAAA,IAC3D;AAEA,SAAK,SAAS,OAAO,SAAS;AAC9B,SAAK,KAAK,kBAAkB,EAAE,UAAU,CAAC;AAEzC,SAAK;AAAA,MACH;AAAA,MACA,kBAAkB,SAAS,6BAA6B,KAAK,SAAS,IAAI;AAAA,IAC5E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAmC;AACjC,WAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,MACpD,IAAI,EAAE;AAAA,MACN,OAAO,EAAE;AAAA,MACT,WAAW,EAAE;AAAA,MACb,OAAO,EAAE,GAAG,EAAE,MAAM;AAAA,IACtB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAA6C;AACtD,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,QAAS,QAAO;AAErB,WAAO;AAAA,MACL,IAAI,QAAQ;AAAA,MACZ,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB,OAAO,EAAE,GAAG,QAAQ,MAAM;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,SAAK,IAAI,QAAQ,wBAAwB;AACzC,UAAM,aAAa,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC;AAClD,UAAM,QAAQ,IAAI,WAAW,IAAI,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC,CAAC;AAC/D,SAAK,IAAI,QAAQ,uBAAuB;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,eAAuB;AACzB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,oBAAoB,IAAS,WAAkC;AAC3E,QAAI,GAAG,sBAAsB,WAAY;AAEzC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,UAAU,WAAW,MAAM;AAC/B,gBAAQ;AAAA,MACV,GAAG,SAAS;AAEZ,SAAG,wBAAwB,UAAU,CAAC,UAAkB;AACtD,YAAI,UAAU,YAAY;AACxB,uBAAa,OAAO;AACpB,kBAAQ;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAkB,SAAuC;AACrE,SAAK;AAAA,MACH;AAAA,MACA,sCAAsC,QAAQ,EAAE,aAAa,KAAK,QAAQ,OAAO,aAAa,KAAK,QAAQ,OAAO;AAAA,IACpH;AAIA,YAAQ,eAAe;AAAA,MACrB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ,YAAY,SACrB,EAAE,SAAS,KAAK,QAAQ,QAAQ,IAChC;AAAA,IACN;AAGA,SAAK,mBAAmB,OAAO,EAAE,MAAM,CAAC,QAAQ;AAC9C,WAAK,IAAI,SAAS,gCAAgC,QAAQ,EAAE,KAAK,GAAG,EAAE;AACtE,WAAK,aAAa,QAAQ,EAAE,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,mBAAmB,SAAuC;AACtE,QAAI,CAAC,QAAQ,cAAc;AACzB,WAAK,IAAI,QAAQ,gCAAgC,QAAQ,EAAE,EAAE;AAC7D;AAAA,IACF;AAEA,SAAK,IAAI,QAAQ,mCAAmC,QAAQ,EAAE,EAAE;AAEhE,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,UAAM,EAAE,WAAW,UAAU,IAAI;AAEjC,QAAI,iBAAiB,KAAK,MAAM,KAAK,OAAO,IAAI,KAAK;AACrD,QAAI,YAAY,KAAK,MAAM,KAAK,OAAO,IAAI,UAAU;AACrD,UAAM,iBAAiB;AACvB,QAAI,iBAAiB;AACrB,QAAI,cAAc,KAAK,IAAI;AAC3B,QAAI,0BAA0B;AAC9B,QAAI,cAAc;AAElB,QAAI;AACF,WAAK,IAAI,QAAQ,mCAAmC,QAAQ,EAAE,EAAE;AAEhE,uBAAiB,SAAS,QAAQ,cAAc;AAC9C,YAAI,QAAQ,UAAU,kBAAkB,QAAQ,UAAU,UAAU;AAClE,eAAK;AAAA,YACH;AAAA,YACA,WAAW,QAAQ,EAAE,aAAa,QAAQ,KAAK;AAAA,UACjD;AACA;AAAA,QACF;AAEA,YAAI,MAAM,OAAO;AAIf,kBAAQ,MAAM;AAAA,QAChB,OAAO;AAEL,cAAI,MAAM,MAAM;AAEd,gBAAI,CAAC,QAAQ,cAAc,MAAM,WAAW;AAC1C,oBAAM,WAAW,wBAAwB,MAAM,IAAI;AACnD,sBAAQ,aAAc,YAAY,MAAM;AACxC,mBAAK,IAAI,QAAQ,yBAAyB,QAAQ,UAAU,EAAE;AAG9D,kBACE,QAAQ,oBACR,QAAQ,iBAAiB,eAAe,QACxC;AACA,sBAAM,YAAY,KAAK,UAAU;AAAA,kBAC/B,MAAM;AAAA,kBACN,OAAO,QAAQ;AAAA,kBACf,OAAO,MAAM,SAAS;AAAA,kBACtB,QAAQ,MAAM,UAAU;AAAA,gBAC1B,CAAC;AACD,wBAAQ,iBAAiB,KAAK,SAAS;AAAA,cACzC;AAAA,YACF;AAGA,gBAAI,MAAM,gBAAgB,iBAAiB,GAAG;AAC5C,oBAAM,cAAc,MAAM,eAAe;AACzC,oBAAM,aAAa,KAAK;AAAA,gBACrB,cAAc,MAAW;AAAA,cAC5B;AACA,0BAAa,YAAY,eAAgB;AAAA,YAC3C,OAAO;AACL,0BAAa,YAAY,QAAU;AAAA,YACrC;AACA,6BAAiB,MAAM,gBAAgB;AAEvC,gBAAI,QAAQ,eAAe,QAAQ;AAGjC,oBAAM,YAAY,QAAQ,eAAe;AACzC,oBAAM,WAAW,QAAQ,eAAe;AAGxC,oBAAM,cACJ,cAAc,eACd,aAAa,eACb,aAAa;AAEf,kBAAI,CAAC,aAAa;AAEhB,oBAAI,cAAc,IAAI;AACpB,uBAAK;AAAA,oBACH;AAAA,oBACA,gDAAgD,WAAW;AAAA,kBAC7D;AAAA,gBACF;AACA;AACA;AAAA,cACF;AAGA,oBAAM,cAAc,MAAM,KAAK;AAAA,gBAC7B;AAAA,gBACA;AAAA,gBACA,MAAM;AAAA,gBACN;AAAA,gBACA;AAAA,cACF;AACA,+BAAkB,iBAAiB,cAAe;AAClD,yCAA2B;AAC3B;AACA,sBAAQ,MAAM;AACd,sBAAQ,MAAM,aAAa,MAAM,KAAK;AAAA,YACxC,WAAW,QAAQ,eAAe,QAAQ;AAExC,oBAAM,OAAO,MAAM,KAAK;AAAA,gBACtB;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,cACF;AACA,kBAAI,MAAM;AACR;AACA;AACA,wBAAQ,MAAM;AACd,wBAAQ,MAAM,aAAa,MAAM,KAAK;AAAA,cACxC;AAAA,YACF;AAGA,kBAAM,MAAM,KAAK,IAAI;AACrB,gBAAI,MAAM,eAAe,KAAM;AAC7B,mBAAK;AAAA,gBACH;AAAA,gBACA,kBAAkB,QAAQ,EAAE,KAAK,QAAQ,UAAU,WAAW,QAAQ,MAAM,WAAW,YAAY,uBAAuB,aAAa,KAAK,MAAM,QAAQ,MAAM,YAAY,IAAI,CAAC;AAAA,cACnL;AACA,4BAAc;AACd,wCAA0B;AAAA,YAC5B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK;AAAA,QACH;AAAA,QACA,oCAAoC,QAAQ,EAAE,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AAEA,SAAK,IAAI,QAAQ,mCAAmC,QAAQ,EAAE,EAAE;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,cACZ,SACA,QACA,WACA,gBACA,WACiB;AAEjB,UAAM,SAAS,gBAAoB,SAAS;AAC5C,UAAM,WAAW,yBAA6B,MAAM;AAGpD,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,WAAqB,CAAC;AAE5B,eAAW,OAAO,UAAU;AAC1B,YAAM,KAAK,IAAI,CAAC,KAAK,KAAK;AAC1B,eAAS,KAAK,CAAC;AACf,UAAI,MAAM,GAAG;AACX,iBAAS;AACT,gBAAQ,cAAc;AAAA,MACxB;AACA,UAAI,MAAM,GAAG;AACX,iBAAS;AACT,gBAAQ,cAAc;AAAA,MACxB;AACA,UAAI,MAAM,EAAG,UAAS;AAAA,IACxB;AAGA,QAAI,QAAQ,MAAM,cAAc,IAAI;AAClC,WAAK;AAAA,QACH;AAAA,QACA,2BAA2B,SAAS,KAAK,GAAG,CAAC;AAAA,MAC/C;AAAA,IACF;AAGA,UAAM,aAAa;AACnB,QAAI,UAAU;AAGd,QAAI,WAAW,CAAC,UAAU,CAAC,SAAS;AAClC,YAAM,UAAoB,CAAC;AAC3B,UAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAQ,KAAK,QAAQ,WAAW;AAChC,aAAK,IAAI,SAAS,oCAAoC;AAAA,MACxD;AACA,UAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAQ,KAAK,QAAQ,WAAW;AAChC,aAAK,IAAI,SAAS,oCAAoC;AAAA,MACxD;AACA,UAAI,QAAQ,SAAS,GAAG;AACtB,kBAAU,CAAC,GAAG,SAAS,GAAG,QAAQ;AAAA,MACpC,WAAW,CAAC,QAAQ,eAAe,CAAC,QAAQ,aAAa;AAEvD,aAAK;AAAA,UACH;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,qBAAqB;AAChC,UAAI,UAAU,QAAQ,eAAe,QAAQ,aAAa;AACxD,gBAAQ,sBAAsB;AAC9B,aAAK;AAAA,UACH;AAAA,UACA;AAAA,QACF;AAAA,MAEF,WAAW,QAAQ;AACjB,aAAK;AAAA,UACH;AAAA,UACA;AAAA,QACF;AACA,eAAO;AAAA,MACT,OAAO;AAEL,YAAI,QAAQ,MAAM,cAAc,GAAG;AACjC,eAAK;AAAA,YACH;AAAA,YACA,oBAAoB,QAAQ,MAAM,WAAW;AAAA,UAC/C;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI,mBAAmB;AACvB,QAAI,gBAAgB;AAGpB,UAAM,OAAO,QAAQ,WAAW,QAAQ;AAExC,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,UAAU,QAAQ,CAAC;AACzB,UAAI,QAAQ,WAAW,EAAG;AAE1B,YAAM,aAAa,MAAM,QAAQ,SAAS;AAC1C,YAAM,UAAU,eAAe,OAAO;AAGtC,UAAI,YAAY,EAAG;AAGnB,YAAM,aAAa,KAAK;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAGA,UAAI,QAAQ,MAAM,cAAc,GAAG;AACjC,aAAK;AAAA,UACH;AAAA,UACA,OAAO,CAAC,UAAU,OAAO,UAAU,QAAQ,MAAM,gBAAgB,WAAW,MAAM;AAAA,QACpF;AAAA,MACF;AAEA,iBAAW,aAAa,YAAY;AAClC,YAAI;AACF,kBAAQ,WAAW,SAAS,SAAS;AACrC,0BAAiB,gBAAgB,IAAK;AACtC;AAAA,QACF,SAAS,KAAK;AACZ,eAAK;AAAA,YACH;AAAA,YACA,wCAAwC,QAAQ,EAAE,KAAK,GAAG;AAAA,UAC5D;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,QAAQ,MAAM,cAAc,GAAG;AACjC,WAAK;AAAA,QACH;AAAA,QACA,8BAA8B,QAAQ,MAAM,YAAY,gBAAgB,QAAQ,cAAc,KAAK,aAAa,OAAO,SAAS,aAAa,UAAU;AAAA,MACzJ;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,6BACZ,SACA,OACA,aACA,OACkB;AAClB,QAAI,CAAC,QAAQ,kBAAkB;AAC7B,UAAI,gBAAgB,GAAG;AACrB,aAAK,IAAI,QAAQ,qCAAqC,QAAQ,EAAE,EAAE;AAAA,MACpE;AACA,aAAO;AAAA,IACT;AAEA,QAAI,QAAQ,iBAAiB,eAAe,QAAQ;AAClD,UAAI,gBAAgB,GAAG;AACrB,aAAK;AAAA,UACH;AAAA,UACA,2CAA2C,QAAQ,EAAE,KAAK,QAAQ,iBAAiB,UAAU;AAAA,QAC/F;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,oBAAoB,MAAM,IAAI;AAC/C,QAAI,aAAa,MAAM,eAAe;AACtC,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,WAAqB,CAAC;AAE5B,eAAW,WAAW,UAAU;AAC9B,UAAI,QAAQ,WAAW,EAAG;AAE1B,UAAI,UAAU,QAAQ;AACpB,cAAM,UAAUD,gBAAe,OAAO;AACtC,iBAAS,KAAK,OAAO;AAErB,YAAI,YAAY,IAAI;AAClB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,IAAI;AAClB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,IAAI;AAClB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,MAAM,YAAY,IAAI;AACpC,mBAAS;AACT,uBAAa;AAAA,QACf;AAAA,MACF,OAAO;AAEL,cAAM,UAAU,eAAe,OAAO;AACtC,iBAAS,KAAK,OAAO;AAErB,YAAI,YAAY,GAAG;AACjB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,GAAG;AACjB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,GAAG;AACjB,mBAAS;AACT,uBAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAGA,QAAI,cAAc,GAAG;AACnB,WAAK;AAAA,QACH;AAAA,QACA,GAAG,KAAK,UAAU,WAAW,gBAAgB,SAAS,KAAK,GAAG,CAAC,YAAY,MAAM,WAAW,MAAM,WAAW,MAAM;AAAA,MACrH;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,qBAAqB;AAChC,UAAI,UAAU,QAAQ;AAEpB,YAAI,UAAU,QAAQ,eAAe,QAAQ,aAAa;AACxD,kBAAQ,sBAAsB;AAC9B,eAAK;AAAA,YACH;AAAA,YACA;AAAA,UACF;AAAA,QACF,WAAW,UAAU,QAAQ;AAE3B,eAAK,IAAI,SAAS,gDAAgD;AAClE,iBAAO;AAAA,QACT,WAAW,QAAQ;AAEjB,eAAK,IAAI,SAAS,sCAAsC;AACxD,iBAAO;AAAA,QACT,OAAO;AAEL,cAAI,cAAc,IAAI;AACpB,iBAAK;AAAA,cACH;AAAA,cACA,0BAA0B,WAAW;AAAA,YACvC;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,MACF,OAAO;AAEL,YACE,UACA,QAAQ,eACR,QAAQ,eACR,QAAQ,aACR;AACA,kBAAQ,sBAAsB;AAC9B,eAAK;AAAA,YACH;AAAA,YACA;AAAA,UACF;AAAA,QACF,WAAW,UAAU,UAAU,QAAQ;AACrC,eAAK,IAAI,SAAS,gDAAgD;AAClE,iBAAO;AAAA,QACT,WAAW,QAAQ;AACjB,eAAK,IAAI,SAAS,gDAAgD;AAClE,iBAAO;AAAA,QACT,OAAO;AACL,cAAI,cAAc,IAAI;AACpB,iBAAK;AAAA,cACH;AAAA,cACA,0BAA0B,WAAW;AAAA,YACvC;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,MAAM;AAEtB,QAAI,QAAQ;AACV,UAAI,UAAU,WAAW,CAAC,UAAU,CAAC,SAAS;AAE5C,cAAM,QAAkB,CAAC;AACzB,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,MAAM,SAAS,GAAG;AACpB,sBAAY,OAAO,OAAO,CAAC,GAAG,OAAO,MAAM,IAAI,CAAC;AAChD,eAAK,IAAI,SAAS,6CAA6C;AAAA,QACjE;AAAA,MACF,WAAW,UAAU,WAAW,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS;AAE9D,cAAM,QAAkB,CAAC;AACzB,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,MAAM,SAAS,GAAG;AACpB,sBAAY,OAAO,OAAO,CAAC,GAAG,OAAO,MAAM,IAAI,CAAC;AAChD,eAAK,IAAI,SAAS,iDAAiD;AAAA,QACrE;AAAA,MACF;AAAA,IACF;AAGA,UAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,WAAO,cAAc,aAAa,CAAC;AACnC,WAAO,cAAc,MAAM,eAAe,MAAM,eAAe,MAAO,GAAG,CAAC;AAC1E,WAAO,WAAW,UAAU,SAAS,IAAO,GAAM,CAAC;AACnD,WAAO,WAAW,aAAa,IAAI,GAAG,CAAC;AACvC,WAAO,cAAc,GAAG,EAAE;AAG1B,UAAM,SAAS,OAAO,OAAO,CAAC,QAAQ,SAAS,CAAC;AAGhD,QAAI,cAAc,GAAG;AACnB,WAAK;AAAA,QACH;AAAA,QACA,WAAW,KAAK,UAAU,WAAW,KAAK,OAAO,MAAM,oBAAoB,UAAU;AAAA,MACvF;AAAA,IACF;AAGA,UAAM,iBAAiB;AAEvB,QAAI;AACF,UAAI,OAAO,UAAU,gBAAgB;AACnC,gBAAQ,iBAAiB,KAAK,MAAM;AAAA,MACtC,OAAO;AAEL,cAAM,cAAc,KAAK,KAAK,OAAO,SAAS,cAAc;AAC5D,iBAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,gBAAM,QAAQ,IAAI;AAClB,gBAAM,MAAM,KAAK,IAAI,QAAQ,gBAAgB,OAAO,MAAM;AAC1D,gBAAM,QAAQ,OAAO,SAAS,OAAO,GAAG;AAGxC,gBAAM,cAAc,OAAO,MAAM,CAAC;AAClC,sBAAY,WAAW,GAAG,CAAC;AAC3B,sBAAY,WAAW,aAAa,CAAC;AAErC,kBAAQ,iBAAiB,KAAK,OAAO,OAAO,CAAC,aAAa,KAAK,CAAC,CAAC;AAAA,QACnE;AAAA,MACF;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,iBAAiB,KAAK,UAAU,WAAW,KAAK,GAAG,EAAE;AACvE,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,cACZ,SACA,OACA,aACe;AACf,QAAI,CAAC,QAAQ,kBAAkB;AAC7B,UAAI,gBAAgB,GAAG;AACrB,aAAK,IAAI,QAAQ,qCAAqC,QAAQ,EAAE,EAAE;AAAA,MACpE;AACA;AAAA,IACF;AAEA,QAAI,QAAQ,iBAAiB,eAAe,QAAQ;AAClD,UAAI,gBAAgB,GAAG;AACrB,aAAK;AAAA,UACH;AAAA,UACA,2CAA2C,QAAQ,EAAE,KAAK,QAAQ,iBAAiB,UAAU;AAAA,QAC/F;AAAA,MACF;AACA;AAAA,IACF;AAGA,QAAI,aAAa,MAAM,eAAe;AAGtC,QAAI,CAAC,cAAc,MAAM,eAAe,QAAW;AACjD,YAAM,WAAW,oBAAoB,MAAM,IAAI;AAC/C,iBAAW,WAAW,UAAU;AAC9B,YAAI,QAAQ,WAAW,EAAG;AAC1B,cAAM,UAAUA,gBAAe,OAAO;AAEtC,YACE,YAAY,MACZ,YAAY,MACZ,YAAY,MACZ,YAAY,MACZ,YAAY,IACZ;AACA,uBAAa;AACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,WAAO,cAAc,aAAa,CAAC;AACnC,WAAO,cAAc,MAAM,eAAe,MAAM,eAAe,MAAO,GAAG,CAAC;AAC1E,WAAO,WAAW,GAAM,CAAC;AACzB,WAAO,WAAW,aAAa,IAAI,GAAG,CAAC;AACvC,WAAO,cAAc,GAAG,EAAE;AAG1B,UAAM,SAAS,OAAO,OAAO,CAAC,QAAQ,MAAM,IAAI,CAAC;AAGjD,QAAI,cAAc,GAAG;AACnB,WAAK;AAAA,QACH;AAAA,QACA,uBAAuB,WAAW,KAAK,OAAO,MAAM,oBAAoB,UAAU;AAAA,MACpF;AAAA,IACF;AAGA,UAAM,iBAAiB;AAEvB,QAAI;AACF,UAAI,OAAO,UAAU,gBAAgB;AACnC,gBAAQ,iBAAiB,KAAK,MAAM;AAAA,MACtC,OAAO;AAEL,cAAM,cAAc,KAAK,KAAK,OAAO,SAAS,cAAc;AAC5D,iBAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,gBAAM,QAAQ,IAAI;AAClB,gBAAM,MAAM,KAAK,IAAI,QAAQ,gBAAgB,OAAO,MAAM;AAC1D,gBAAM,QAAQ,OAAO,SAAS,OAAO,GAAG;AAGxC,gBAAM,cAAc,OAAO,MAAM,CAAC;AAClC,sBAAY,WAAW,GAAG,CAAC;AAC3B,sBAAY,WAAW,aAAa,CAAC;AAErC,kBAAQ,iBAAiB,KAAK,OAAO,OAAO,CAAC,aAAa,KAAK,CAAC,CAAC;AAAA,QACnE;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,6BAA6B,WAAW,KAAK,GAAG,EAAE;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBACN,QACA,SACA,gBACA,WACA,QACA,MACO;AACP,UAAM,EAAE,WAAW,UAAU,IAAI;AACjC,UAAM,MAAM;AAEZ,UAAM,UAAiB,CAAC;AAExB,QAAI,QAAQ,UAAU,KAAK;AAEzB,YAAM,SAAS,IAAI,UAAU;AAC7B,aAAO,cAAc;AACrB,aAAO,iBAAiB;AACxB,aAAO,YAAY;AACnB,aAAO,SAAS;AAChB,aAAO,OAAO;AAEd,cAAQ,KAAK,IAAI,UAAU,QAAQ,OAAO,CAAC;AAAA,IAC7C,OAAO;AAEL,YAAM,YAAY,QAAQ,CAAC;AAC3B,YAAM,UAAU,YAAY;AAC5B,YAAM,MAAM,YAAY;AAGxB,YAAM,eAAe,MAAM,MAAM;AAEjC,UAAI,SAAS;AACb,UAAI,UAAU;AAEd,aAAO,SAAS,QAAQ,QAAQ;AAC9B,cAAM,YAAY,QAAQ,SAAS;AACnC,cAAM,YAAY,KAAK,IAAI,WAAW,MAAM,CAAC;AAC7C,cAAM,SAAS,SAAS,aAAa,QAAQ;AAG7C,YAAI,WAAW;AACf,YAAI,QAAS,aAAY;AACzB,YAAI,OAAQ,aAAY;AAGxB,cAAM,YAAY,OAAO,MAAM,IAAI,SAAS;AAC5C,kBAAU,CAAC,IAAI;AACf,kBAAU,CAAC,IAAI;AACf,gBAAQ,KAAK,WAAW,GAAG,QAAQ,SAAS,SAAS;AAErD,cAAM,SAAS,IAAI,UAAU;AAC7B,eAAO,cAAc;AACrB,eAAO,iBAAkB,iBAAiB,QAAQ,SAAU;AAC5D,eAAO,YAAY;AACnB,eAAO,SAAS,UAAU;AAC1B,eAAO,OAAO;AAEd,gBAAQ,KAAK,IAAI,UAAU,QAAQ,SAAS,CAAC;AAE7C,kBAAU;AACV,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAAc,SAAuC;AACjE,QAAI;AACF,cAAQ,WAAW,IAAI,SAAS;AAAA,QAC9B,KAAK,KAAK,QAAQ;AAAA,QAClB,SAAS,KAAK,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,QAAQ,SAAS,MAAM;AAC7B,WAAK,IAAI,QAAQ,gCAAgC,QAAQ,EAAE,EAAE;AAAA,IAC/D,SAAS,KAAK;AACZ,WAAK;AAAA,QACH;AAAA,QACA,wCAAwC,QAAQ,EAAE,KAAK,GAAG;AAAA,MAC5D;AAEA,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,IACN,OACA,SACM;AACN,QAAI,KAAK,QAAQ,QAAQ;AACvB,WAAK,QAAQ,OAAO,OAAO,OAAO;AAAA,IACpC;AAAA,EACF;AACF;;;ACv1CA,SAAS,gBAAAE,qBAAoB;AAC7B,OAAO,QAAQ;AACf,OAAO,SAAS;AAChB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,SAAAC,cAAgC;AA+DzC,SAASC,qBAAoB,MAAwB;AACnD,QAAM,QAAkB,CAAC;AACzB,QAAM,MAAM,KAAK;AAEjB,QAAM,YAAY,CAAC,SAAyB;AAC1C,aAAS,IAAI,MAAM,IAAI,IAAI,KAAK,KAAK;AACnC,UAAI,KAAK,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,GAAM;AAC5C,YAAI,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AACjC,YAAI,IAAI,IAAI,OAAO,KAAK,IAAI,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM;AACzD,iBAAO;AAAA,MACX;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,CAAC,MAAsB;AAC5C,QAAI,IAAI,IAAI,OAAO,KAAK,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,GAAM;AAC3D,UAAI,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AACjC,UAAI,IAAI,IAAI,OAAO,KAAK,IAAI,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AAAA,IAC1E;AACA,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,UAAU,CAAC;AACvB,MAAI,QAAQ,EAAG,QAAO;AAEtB,SAAO,SAAS,GAAG;AACjB,UAAM,QAAQ,eAAe,KAAK;AAClC,QAAI,CAAC,MAAO;AACZ,UAAM,WAAW,QAAQ;AACzB,QAAI,OAAO,UAAU,QAAQ;AAC7B,QAAI,OAAO,EAAG,QAAO;AACrB,QAAI,WAAW,KAAM,OAAM,KAAK,KAAK,SAAS,UAAU,IAAI,CAAC;AAC7D,YAAQ,OAAO,MAAM,OAAO;AAAA,EAC9B;AAEA,SAAO;AACT;AAWA,SAAS,iBAAiB,OAAiB,QAAyB;AAClE,QAAM,OAAOA,qBAAoB,MAAM;AAEvC,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,OAAO,IAAI,WAAW,EAAG;AAC9B,QAAI,UAAU,QAAQ;AACpB,YAAM,UAAU,IAAI,CAAC,IAAK;AAE1B,UAAI,YAAY,EAAG,QAAO;AAAA,IAC5B,OAAO;AACL,YAAM,UAAW,IAAI,CAAC,KAAM,IAAK;AAEjC,UAAI,WAAW,MAAM,WAAW,GAAI,QAAO;AAAA,IAC7C;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,aAAa,OAAiB,QAAyB;AAC9D,QAAM,OAAOA,qBAAoB,MAAM;AACvC,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,OAAO,IAAI,WAAW,EAAG;AAC9B,QAAI,UAAU,QAAQ;AACpB,YAAM,UAAU,IAAI,CAAC,IAAK;AAC1B,UAAI,YAAY,KAAK,YAAY,EAAG,QAAO;AAAA,IAC7C,OAAO;AACL,YAAM,UAAW,IAAI,CAAC,KAAM,IAAK;AACjC,UAAI,YAAY,MAAM,YAAY,MAAM,YAAY,GAAI,QAAO;AAAA,IACjE;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,YAAY,OAAiB,QAA0B;AAC9D,QAAM,OAAOA,qBAAoB,MAAM;AACvC,SAAO,KAAK,IAAI,CAAC,QAAQ;AACvB,QAAI,UAAU,QAAQ;AACpB,aAAQ,IAAI,CAAC,KAAM,IAAK;AAAA,IAC1B,OAAO;AACL,aAAO,IAAI,CAAC,IAAK;AAAA,IACnB;AAAA,EACF,CAAC;AACH;AAMO,IAAM,oBAAN,cAAgCC,cAAa;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAKT,YAA2B;AAAA,EAC3B,iBAA0B;AAAA,EAC1B,eAA8B;AAAA,EAC9B,iBAAgC;AAAA,EAEhC,QAAkC;AAAA,EAClC,QAAyB;AAAA,EACzB,iBAAyB;AAAA,EACzB,SAA8B;AAAA,EAC9B,eAA0D;AAAA,EAC1D,cAAoC;AAAA,EACpC,YAAyB;AAAA,EACzB,YAA2B;AAAA,EAEnC,YAAY,SAAmC;AAC7C,UAAM;AACN,SAAK,MAAM,QAAQ;AACnB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,aAAa,QAAQ,cAAc;AAExC,QAAI,QAAQ,WAAW;AACrB,WAAK,YAAY,QAAQ;AACzB,WAAK,iBAAiB;AAAA,IACxB;AAEA,SAAK,sBAAsB,QAAQ;AACnC,SAAK,MAAM,QAAQ,WAAW,MAAM;AAAA,IAAC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,UAAU,aAAa,KAAK,UAAU,YAAY;AACzD;AAAA,IACF;AAEA,SAAK,QAAQ;AACb,SAAK,YAAY;AAEjB,QAAI;AAEF,UAAI,CAAC,KAAK,WAAW;AACnB,aAAK,YAAY,MAAM,IAAI;AAAA,UACzB,KAAK,KAAK,GAAG,OAAO,GAAG,gBAAgB,KAAK,OAAO,GAAG;AAAA,QACxD;AACA,aAAK,iBAAiB;AAAA,MACxB,OAAO;AACL,cAAM,IAAI,MAAM,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,MACrD;AAEA,WAAK,eAAe,KAAK,KAAK,KAAK,WAAW,eAAe;AAC7D,WAAK,iBAAiB,KAAK,KAAK,KAAK,WAAW,iBAAiB;AAEjE,WAAK,IAAI,QAAQ,0BAA0B,KAAK,SAAS,EAAE;AAG3D,UAAI,KAAK,qBAAqB;AAC5B,aAAK,eAAe,KAAK,2BAA2B,KAAK,mBAAmB;AAAA,MAC9E,OAAO;AAEL,aAAK,eAAe;AAAA,UAClB,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI;AAAA,QAC7C;AAAA,MACF;AAGA,WAAK,cAAc,KAAK,mBAAmB;AAC3C,WAAK,YAAY,oBAAI,KAAK;AAC1B,WAAK,QAAQ;AAEb,WAAK,KAAK,WAAW,EAAE,WAAW,KAAK,UAAU,CAAC;AAAA,IACpD,SAAS,KAAK;AACZ,WAAK,QAAQ;AACb,WAAK,YAAY,OAAO,GAAG;AAC3B,WAAK,IAAI,SAAS,wBAAwB,GAAG,EAAE;AAC/C,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,KAAK,UAAU,UAAU,KAAK,UAAU,WAAW;AACrD;AAAA,IACF;AAEA,SAAK,QAAQ;AACb,SAAK,IAAI,QAAQ,qBAAqB;AAGtC,QAAI;AACF,WAAK,QAAQ,OAAO,IAAI;AAAA,IAC1B,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,WAAK,QAAQ,KAAK,SAAS;AAAA,IAC7B,QAAQ;AAAA,IAER;AACA,SAAK,SAAS;AAGd,QAAI,KAAK,cAAc;AACrB,UAAI;AACF,cAAM,KAAK,aAAa,OAAO,MAAgB;AAAA,MACjD,QAAQ;AAAA,MAER;AACA,WAAK,eAAe;AAAA,IACtB;AAGA,QAAI,KAAK,aAAa;AACpB,UAAI;AACF,cAAM,KAAK;AAAA,MACb,QAAQ;AAAA,MAER;AACA,WAAK,cAAc;AAAA,IACrB;AAGA,QAAI,KAAK,kBAAkB,KAAK,WAAW;AACzC,UAAI;AACF,cAAM,IAAI,GAAG,KAAK,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAC/D,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,SAAK,QAAQ;AACb,SAAK,KAAK,SAAS;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,YAA6B;AAC3B,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,gBAAgB,KAAK;AAAA,MACrB,eAAe,KAAK,WAAW,QAAQ,CAAC,KAAK,OAAO;AAAA,MACpD,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,eAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,YAAoB,KAAyB;AACjE,QAAI,CAAC,KAAK,aAAc,QAAO;AAE/B,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,WAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAI,GAAG,WAAW,KAAK,YAAY,GAAG;AACpC,eAAO;AAAA,MACT;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UACJ,WACuD;AACvD,QAAI,CAAC,KAAK,UAAW,QAAO;AAG5B,UAAM,OAAO,UAAU,QAAQ,QAAQ,EAAE;AACzC,QAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,GAAG,GAAG;AAC7C,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,KAAK,KAAK,KAAK,WAAW,IAAI;AAC/C,QAAI,CAAC,GAAG,WAAW,QAAQ,GAAG;AAC5B,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,IAAI,SAAS,QAAQ;AACxC,QAAI,cAAc;AAClB,QAAI,KAAK,SAAS,OAAO,GAAG;AAC1B,oBAAc;AAAA,IAChB,WAAW,KAAK,SAAS,KAAK,GAAG;AAC/B,oBAAc;AAAA,IAChB;AAEA,WAAO,EAAE,MAAM,YAAY;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAe,2BACb,aAOgB;AAQhB,UAAM,QAAiB,CAAC;AACxB,QAAI,UAA+B;AACnC,QAAI,OAAO;AAEX,UAAM,UAAU,CAAC,OAAc;AAC7B,YAAM,KAAK,EAAE;AACb,gBAAU;AACV,gBAAU;AAAA,IACZ;AAEA,UAAM,UAAU,MAAM;AACpB,aAAO;AACP,gBAAU;AACV,gBAAU;AAAA,IACZ;AAEA,UAAM,UAAU,MAAM;AACpB,aAAO;AACP,gBAAU;AACV,gBAAU;AAAA,IACZ;AAEA,gBAAY,GAAG,mBAAmB,OAAO;AACzC,gBAAY,GAAG,SAAS,OAAO;AAC/B,gBAAY,GAAG,SAAS,OAAO;AAE/B,QAAI;AACF,aAAO,CAAC,MAAM;AACZ,YAAI,MAAM,WAAW,GAAG;AACtB,gBAAM,IAAI,QAAc,CAAC,MAAM;AAAE,sBAAU;AAAA,UAAG,CAAC;AAAA,QACjD;AACA,eAAO,MAAM,SAAS,GAAG;AACvB,gBAAM,QAAQ,MAAM,MAAM;AAC1B,gBAAM;AAAA,YACJ,OAAO;AAAA,YACP,MAAM,MAAM;AAAA,YACZ,WAAW,MAAM;AAAA,YACjB,YAAY,MAAM;AAAA,YAClB,cAAc,MAAM;AAAA,UACtB;AAAA,QACF;AAAA,MACF;AAAA,IACF,UAAE;AACA,kBAAY,eAAe,mBAAmB,OAAO;AACrD,kBAAY,eAAe,SAAS,OAAO;AAC3C,kBAAY,eAAe,SAAS,OAAO;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,MAAc,qBAAoC;AAChD,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,gBAAgB,CAAC,KAAK,gBAAgB;AACpE;AAAA,IACF;AAEA,QAAI,gBAAgB;AACpB,QAAI,mBAA6B,CAAC;AAClC,UAAM,+BAA+B;AAErC,UAAM,mBAAmB,CAAC,OAAiB,WAAmB;AAC5D,YAAM,OAAOD,qBAAoB,MAAM;AACvC,iBAAW,OAAO,MAAM;AACtB,YAAI,CAAC,OAAO,IAAI,WAAW,EAAG;AAC9B,YAAI,UAAU,QAAQ;AACpB,gBAAM,IAAI,IAAI,CAAC,IAAK;AACpB,cAAI,MAAM,KAAK,MAAM,GAAG;AACtB,6BAAiB;AAAA,cACf,OAAO,OAAO,CAAC,OAAO,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AAAA,YAChD;AAAA,UACF;AAAA,QACF,OAAO;AACL,gBAAM,IAAK,IAAI,CAAC,KAAM,IAAK;AAC3B,cAAI,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AACpC,6BAAiB;AAAA,cACf,OAAO,OAAO,CAAC,OAAO,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AAAA,YAChD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,UAAI,iBAAiB,SAAS,IAAI;AAChC,2BAAmB,iBAAiB,MAAM,GAAG;AAAA,MAC/C;AAAA,IACF;AAEA,QAAI;AACF,uBAAiB,SAAS,KAAK,cAAc;AAC3C,YAAI,KAAK,UAAU,UAAW;AAG9B,YAAI,MAAM,MAAO;AACjB,YAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,WAAW,EAAG;AAG5C,YAAI,CAAC,KAAK,OAAO;AACf,gBAAM,WAAW,wBAAwB,MAAM,IAAI;AACnD,gBAAM,WACJ,MAAM,cAAc,SAAS,SAAS;AACxC,eAAK,QAAQ,WACR,SAAS,YAAY,IACtB;AACJ,eAAK;AAAA,YACH;AAAA,YACA,4BAA4B,QAAQ,aAAa,QAAQ,WAAW,KAAK,KAAK;AAAA,UAChF;AACA,eAAK,KAAK,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,QACnD;AAGA,cAAM,SACJ,KAAK,UAAU,SACXE,iBAAoB,MAAM,IAAI,IAC9B,gBAAoB,MAAM,IAAI;AAEpC,aAAK;AAGL,cAAM,YACJ,KAAK,kBAAkB,KACtB,KAAK,kBAAkB,MAAM,KAAK,iBAAiB,OAAO;AAC7D,YAAI,WAAW;AACb,gBAAM,WAAW,YAAY,KAAK,OAAO,MAAM;AAC/C,gBAAM,SAAS,iBAAiB,KAAK,OAAO,MAAM;AAClD,gBAAM,YAAY,aAAa,KAAK,OAAO,MAAM;AACjD,eAAK;AAAA,YACH;AAAA,YACA,aAAa,KAAK,cAAc,WAAW,OAAO,MAAM,cAAc,SAAS,KAAK,GAAG,CAAC,YAAY,MAAM,cAAc,SAAS;AAAA,UACnI;AAAA,QACF;AAEA,yBAAiB,KAAK,OAAO,MAAM;AAKnC,cAAM,aAAa,iBAAiB,KAAK,OAAO,MAAM;AAGtD,YAAI,CAAC,cAAc,CAAC,eAAe;AACjC,cAAI,KAAK,iBAAiB,8BAA8B;AACtD;AAAA,UACF;AACA,eAAK;AAAA,YACH;AAAA,YACA,qBAAqB,KAAK,cAAc;AAAA,UAC1C;AAAA,QACF;AAEA,YAAI,CAAC,eAAe;AAClB,eAAK;AAAA,YACH;AAAA,YACA,0BAA0B,KAAK,KAAK,eAAe,KAAK,cAAc,eAAe,UAAU,cAAc,iBAAiB,MAAM;AAAA,UACtI;AACA,eAAK,SAAS,KAAK,YAAY;AAC/B,0BAAgB;AAChB,eAAK,KAAK,gBAAgB;AAG1B,cAAI;AACF,gBAAI,KAAK,QAAQ,SAAS,CAAC,KAAK,OAAO,MAAM,WAAW;AACtD,yBAAW,MAAM,kBAAkB;AACjC,qBAAK,OAAO,MAAM,MAAM,EAAE;AAAA,cAC5B;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,YAAI,CAAC,KAAK,UAAU,CAAC,KAAK,OAAO,SAAS,KAAK,OAAO,MAAM,WAAW;AACrE,eAAK,IAAI,QAAQ,2CAA2C;AAC5D;AAAA,QACF;AAEA,YAAI;AACF,eAAK,OAAO,MAAM,MAAM,MAAM;AAE9B,cACE,KAAK,iBAAiB,QAAQ,KAC9B,KAAK,kBAAkB,KACtB,KAAK,kBAAkB,MAAM,KAAK,iBAAiB,OAAO,GAC3D;AACA,iBAAK;AAAA,cACH;AAAA,cACA,kBAAkB,KAAK,cAAc,eAAe,OAAO,MAAM;AAAA,YACnE;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,eAAK,IAAI,SAAS,8BAA8B,GAAG,EAAE;AACrD;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,WAAK,IAAI,SAAS,mBAAmB,CAAC,EAAE;AACxC,WAAK,YAAY,OAAO,CAAC;AACzB,WAAK,QAAQ;AACb,WAAK,KAAK,SAAS,CAAC;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,cAA4B;AAClC,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,gBAAgB;AAC9C,YAAM,IAAI,MAAM,uBAAuB;AAAA,IACzC;AAEA,UAAM,QAAQ,KAAK,SAAS;AAE5B,UAAM,OAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,SAAS,SAAS;AAAA,MAC5B;AAAA,MACA;AAAA,IACF;AAEA,QAAI,UAAU,QAAQ;AAEpB,WAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AAEL,WAAK,KAAK,QAAQ,MAAM;AAAA,IAC1B;AAEA,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,KAAK,eAAe;AAAA,MAC3B;AAAA,MACA,OAAO,KAAK,YAAY;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAEA,UAAM,IAAIC,OAAM,KAAK,YAAY,MAAM;AAAA,MACrC,OAAO,CAAC,QAAQ,UAAU,MAAM;AAAA,IAClC,CAAC;AAED,MAAE,GAAG,SAAS,CAAC,QAAQ;AACrB,WAAK,IAAI,SAAS,uBAAuB,GAAG,EAAE;AAC9C,WAAK,KAAK,gBAAgB,GAAG;AAAA,IAC/B,CAAC;AAED,MAAE,QAAQ,GAAG,QAAQ,CAAC,MAAM;AAC1B,YAAM,IAAI,OAAO,KAAK,EAAE,EAAE,KAAK;AAC/B,UAAI,EAAG,MAAK,IAAI,QAAQ,YAAY,CAAC,EAAE;AAAA,IACzC,CAAC;AAED,MAAE,GAAG,QAAQ,CAAC,MAAM,WAAW;AAC7B,WAAK;AAAA,QACH;AAAA,QACA,uBAAuB,QAAQ,GAAG,WAAW,UAAU,GAAG;AAAA,MAC5D;AACA,WAAK,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAAA,IAC7C,CAAC;AAED,WAAO;AAAA,EACT;AACF;;;ACptBA,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,SAAAC,cAAa;AACtB,YAAYC,UAAS;AAsBd,IAAM,sBAAN,cAAkCF,cAKtC;AAAA,EACO;AAAA,EACA,kBAA0C;AAAA,EAC1C,aAAgC;AAAA,EAChC,gBAAiD;AAAA,EACjD,SAAS;AAAA,EACT;AAAA,EACA,mBAAmB,oBAAI,IAAY;AAAA,EAE3C,YAAY,SAAqC;AAC/C,UAAM;AACN,SAAK,UAAU;AAAA,MACb,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,GAAG;AAAA,IACL;AACA,SAAK,SAAS,QAAQ,UAAU;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,SAAK,SAAS;AACd,SAAK,OAAO,MAAM,yDAAyD;AAE3E,QAAI;AAEF,WAAK,kBAAkB,IAAI,gBAAgB;AAAA,QACzC,KAAK,KAAK,QAAQ;AAAA,QAClB,cAAc,KAAK,QAAQ;AAAA,QAC3B,aAAa,KAAK,QAAQ;AAAA,QAC1B,cAAc,KAAK,QAAQ;AAAA,QAC3B,aAAa,KAAK,QAAQ;AAAA,QAC1B,GAAI,KAAK,QAAQ,cAAc,EAAE,aAAa,KAAK,QAAQ,YAAY,IAAI,CAAC;AAAA,QAC5E,GAAI,KAAK,QAAQ,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,QAAQ,IAAI,CAAC;AAAA,QAC9E,GAAI,KAAK,QAAQ,cAAc,SAAY,EAAE,WAAW,KAAK,QAAQ,UAAU,IAAI,CAAC;AAAA,QACpF,QAAQ,KAAK;AAAA,MACf,CAAC;AAED,WAAK,gBAAgB,GAAG,SAAS,CAAC,UAAU;AAC1C,aAAK,OAAO,QAAQ,iDAAiD,KAAK;AAC1E,aAAK,KAAK,SAAS,KAAK;AAAA,MAC1B,CAAC;AAED,WAAK,gBAAgB,GAAG,SAAS,MAAM;AACrC,aAAK,OAAO,MAAM,+CAA+C;AAAA,MACnE,CAAC;AAED,YAAM,KAAK,gBAAgB,MAAM;AAGjC,YAAM,KAAK,gBAAgB;AAE3B,WAAK,OAAO;AAAA,QACV,2CAA2C,KAAK,QAAQ,UAAU,IAAI,KAAK,QAAQ,UAAU,GAAG,KAAK,QAAQ,IAAI;AAAA,MACnH;AAAA,IACF,SAAS,OAAO;AACd,WAAK,SAAS;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAiC;AAE7C,UAAM,gBAAgB,MAAM,KAAK,QAAQ,IAAI,kBAAkB,KAAK,QAAQ,YAAY;AACxF,UAAM,kBAAkB,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,KAAK,QAAQ,YAAY;AACjG,UAAM,QAAQ,iBAAiB,SAAS;AACxC,UAAM,SAAS,iBAAiB,UAAU;AAC1C,UAAM,MAAM,iBAAiB,aAAa;AAG1C,SAAK,aAAiB,kBAAa,CAAC,WAAW;AAC7C,WAAK,qBAAqB,MAAM;AAAA,IAClC,CAAC;AAED,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAK,WAAY,OAAO,KAAK,QAAQ,YAAY,KAAK,QAAQ,YAAY,MAAM;AAC9E,cAAM,UAAU,KAAK,WAAY,QAAQ;AACzC,YAAI,WAAW,OAAO,YAAY,YAAY,UAAU,SAAS;AAC/D,UAAC,KAAK,QAAgB,aAAa,QAAQ;AAAA,QAC7C;AACA,gBAAQ;AAAA,MACV,CAAC;AACD,WAAK,WAAY,GAAG,SAAS,MAAM;AAAA,IACrC,CAAC;AAGD,SAAK,sBAAsB,OAAO,QAAQ,GAAG;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,OAAe,QAAgB,KAAmB;AAC9E,UAAM,UAAU,UAAU,KAAK,QAAQ,UAAU,IAAI,KAAK,QAAQ,UAAU,GAAG,KAAK,QAAQ,IAAI;AAGhG,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MAAa;AAAA,MACb;AAAA,MAAM;AAAA,MACN;AAAA,MAAM;AAAA,MACN;AAAA,MAAQ;AAAA,MACR;AAAA,MAAM;AAAA,MACN;AAAA,MAAe;AAAA,MACf;AAAA,IACF;AAEA,SAAK,OAAO;AAAA,MACV,sDAAsD,WAAW,KAAK,GAAG,CAAC;AAAA,IAC5E;AAEA,SAAK,gBAAgBC,OAAM,UAAU,YAAY;AAAA,MAC/C,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,UAAU;AACxC,WAAK,OAAO,QAAQ,uCAAuC,KAAK;AAChE,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,SAAS;AACvC,UAAI,SAAS,KAAK,SAAS,MAAM;AAC/B,aAAK,OAAO,OAAO,iDAAiD,IAAI,EAAE;AAAA,MAC5E;AAAA,IACF,CAAC;AAGD,SAAK,gBAAiB,GAAG,cAAc,CAAC,UAAkB;AACxD,UAAI,KAAK,eAAe,SAAS,CAAC,KAAK,cAAc,MAAM,WAAW;AACpE,YAAI;AACF,gBAAM,UAAU,KAAK,cAAc,MAAM,MAAM,KAAK;AACpD,cAAI,CAAC,SAAS;AACZ,iBAAK,cAAc,MAAM,KAAK,SAAS,MAAM;AAAA,YAAC,CAAC;AAAA,UACjD;AAAA,QACF,SAAS,OAAO;AACd,gBAAM,OAAQ,OAAe;AAC7B,cAAI,SAAS,WAAW,SAAS,8BAA8B;AAC7D,iBAAK,OAAO,QAAQ,kDAAkD,KAAK;AAAA,UAC7E;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAGD,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,YAAM,SAAS,KAAK,SAAS;AAC7B,UAAI,OAAO,SAAS,OAAO,KAAK,OAAO,SAAS,OAAO,GAAG;AACxD,aAAK,OAAO,QAAQ,wCAAwC,MAAM;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAqB,QAA0B;AACrD,UAAM,WAAW,GAAG,OAAO,aAAa,IAAI,OAAO,UAAU;AAC7D,SAAK,OAAO,MAAM,gDAAgD,QAAQ,EAAE;AAC5E,SAAK,iBAAiB,IAAI,QAAQ;AAClC,SAAK,KAAK,UAAU,QAAQ;AAE5B,WAAO,GAAG,SAAS,MAAM;AACvB,WAAK,iBAAiB,OAAO,QAAQ;AACrC,WAAK,KAAK,sBAAsB,QAAQ;AACxC,WAAK,OAAO,MAAM,mDAAmD,QAAQ,EAAE;AAAA,IACjF,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,UAAU;AAC5B,WAAK,OAAO,QAAQ,4CAA4C,KAAK;AAAA,IACvE,CAAC;AAAA,EAKH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AAEA,SAAK,SAAS;AACd,SAAK,OAAO,MAAM,0CAA0C;AAG5D,QAAI,KAAK,iBAAiB;AACxB,YAAM,KAAK,gBAAgB,KAAK;AAChC,WAAK,kBAAkB;AAAA,IACzB;AAGA,QAAI,KAAK,eAAe;AACtB,UAAI;AACF,aAAK,cAAc,OAAO,IAAI;AAC9B,aAAK,cAAc,KAAK,SAAS;AACjC,mBAAW,MAAM;AACf,cAAI;AACF,iBAAK,eAAe,KAAK,SAAS;AAAA,UACpC,QAAQ;AAAA,UAAC;AAAA,QACX,GAAG,GAAI;AAAA,MACT,QAAQ;AAAA,MAAC;AACT,WAAK,gBAAgB;AAAA,IACvB;AAGA,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAK,YAAY,MAAM,MAAM,QAAQ,CAAC;AAAA,MACxC,CAAC;AACD,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,iBAAiB,MAAM;AAC5B,SAAK,KAAK,OAAO;AACjB,SAAK,OAAO,MAAM,sCAAsC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,aAAqB;AACnB,WAAO,UAAU,KAAK,QAAQ,UAAU,IAAI,KAAK,QAAQ,UAAU,GAAG,KAAK,QAAQ,IAAI;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAyB;AACvB,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AACF;","names":["ff","cleanup","http","spawn","http","spawn","i","sps","pps","spawn","spawn","created","requestedId","sps","pps","address","http","spawn","EventEmitter","EventEmitter","pts","ts","convertToAnnexB","splitAnnexBToNalPayloads","EventEmitter","spawn","http","EventEmitter","http","EventEmitter","spawn","EventEmitter","path","convertToAnnexB","EventEmitter","getH265NalType","EventEmitter","EventEmitter","spawn","parseAnnexBNalUnits","EventEmitter","convertToAnnexB","spawn","EventEmitter","spawn","net"]}
|
|
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/Go2rtcTcpServer.ts","../src/baichuan/stream/BaichuanHttpStreamServer.ts","../src/baichuan/stream/BaichuanMjpegServer.ts","../src/baichuan/stream/MjpegTransformer.ts","../src/baichuan/stream/BaichuanWebRTCServer.ts","../src/baichuan/stream/AacToOpusTranscoder.ts","../src/baichuan/stream/BaichuanHlsServer.ts","../src/multifocal/compositeRtspServer.ts","../src/reolink/baichuan/utils/motionZone.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 // ALL iOS clients need HLS for reliable video clip playback.\n // iOS AVFoundation requires Content-Length + Range support for regular MP4,\n // but generating the full MP4 upfront takes too long (camera download + transcode).\n // HLS delivers segments progressively (~3-5s to first frame vs 25+ seconds).\n // Safari, AVPlayer, and InstalledApp all support HLS natively.\n needsHls: isIos,\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 {\n discoverReolinkDevices,\n type DiscoveredDevice,\n type DiscoveryOptions,\n} from \"./discovery\";\n\nexport interface AutodiscoveryClientOptions extends DiscoveryOptions {\n /** Interval between discovery scans in milliseconds (default: 120000 = 2 minutes) */\n scanIntervalMs?: number;\n /** Whether to start discovery automatically on construction (default: false) */\n autoStart?: boolean;\n /** Called when new (previously unseen) devices are discovered */\n onDeviceDiscovered?: (device: DiscoveredDevice) => void;\n /** Called when an existing device's info is updated (e.g. model filled in) */\n onDeviceUpdated?: (device: DiscoveredDevice) => void;\n}\n\n/**\n * Continuous discovery client for Reolink cameras on the network.\n * Runs periodic scans using all configured discovery methods and maintains\n * an always-up-to-date list of discovered devices.\n *\n * Supports callbacks for new/updated devices, making it ideal for plugins\n * that want to be notified as cameras appear (e.g. battery cameras waking up).\n *\n * @example\n * ```typescript\n * const client = new AutodiscoveryClient({\n * enableArpLookup: true,\n * enableOnvifDiscovery: true,\n * scanIntervalMs: 60_000,\n * autoStart: true,\n * onDeviceDiscovered: (device) => {\n * console.log(`New device: ${device.host} (${device.model})`);\n * },\n * });\n *\n * // Get the current list\n * const devices = client.getDiscoveredDevices();\n *\n * // Stop\n * client.stop();\n * ```\n */\nexport class AutodiscoveryClient {\n private readonly options: AutodiscoveryClientOptions & {\n scanIntervalMs: number;\n };\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 constructor(options: AutodiscoveryClientOptions = {}) {\n this.options = {\n ...options,\n scanIntervalMs: options.scanIntervalMs ?? 120_000,\n autoStart: options.autoStart ?? false,\n };\n\n if (this.options.autoStart) {\n this.start();\n }\n }\n\n /**\n * Start continuous discovery. If already running, does nothing.\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 // Run first scan immediately\n this.performScan();\n\n // Schedule subsequent scans\n this.scheduleNextScan();\n }\n\n /**\n * Stop continuous discovery. If not running, does nothing.\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 * Returns the current list of discovered devices, sorted by host IP.\n */\n getDiscoveredDevices(): DiscoveredDevice[] {\n return Array.from(this.discoveredDevices.values()).sort((a, b) =>\n a.host.localeCompare(b.host),\n );\n }\n\n /**\n * Returns the number of currently discovered devices.\n */\n getDeviceCount(): number {\n return this.discoveredDevices.size;\n }\n\n /**\n * Returns whether continuous discovery is currently running.\n */\n isActive(): boolean {\n return this.isRunning;\n }\n\n /**\n * Force an immediate scan (doesn't wait for the scheduled interval).\n * If a scan is already in progress, waits for it to complete.\n */\n async scanNow(): Promise<void> {\n if (this.currentScanPromise) {\n this.options.logger?.log?.(\n \"[Autodiscovery] Scan already in progress, waiting for completion...\",\n );\n await this.currentScanPromise;\n return;\n }\n\n await this.performScan();\n }\n\n /**\n * Remove a device from the discovered list.\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 * Clear all discovered devices.\n */\n clearDevices(): void {\n const count = this.discoveredDevices.size;\n this.discoveredDevices.clear();\n this.options.logger?.log?.(\n `[Autodiscovery] Removed ${count} device(s) from list`,\n );\n }\n\n private async performScan(): Promise<void> {\n const scanPromise = (async () => {\n try {\n this.options.logger?.log?.(\"[Autodiscovery] Starting scan...\");\n\n const discovered = await discoverReolinkDevices(this.options);\n\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 const updated = this.mergeDeviceInfo(existing, device);\n if (updated) {\n updatedDevices.push(updated);\n }\n } else {\n this.discoveredDevices.set(device.host, { ...device });\n newDevices.push(device);\n }\n }\n\n this.options.logger?.log?.(\n `[Autodiscovery] Scan completed: ${newDevices.length} new, ${updatedDevices.length} updated, total: ${this.discoveredDevices.size}`,\n );\n\n // Fire callbacks\n for (const device of newDevices) {\n this.options.logger?.log?.(\n `[Autodiscovery] NEW DEVICE - ${device.host} | ${device.model ?? \"unknown\"} | ${device.name ?? \"\"} | via ${device.discoveryMethod}`,\n );\n try {\n this.options.onDeviceDiscovered?.(device);\n } catch {\n // ignore callback errors\n }\n }\n for (const device of updatedDevices) {\n try {\n this.options.onDeviceUpdated?.(device);\n } catch {\n // ignore callback errors\n }\n }\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n this.options.logger?.error?.(\n `[Autodiscovery] Error during scan: ${msg}`,\n );\n }\n })();\n\n this.currentScanPromise = scanPromise;\n await scanPromise;\n this.currentScanPromise = null;\n }\n\n private mergeDeviceInfo(\n existing: DiscoveredDevice,\n updated: DiscoveredDevice,\n ): DiscoveredDevice | null {\n let hasChanges = false;\n\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 (\n updated.supportsHttps !== undefined &&\n existing.supportsHttps !== updated.supportsHttps\n ) {\n existing.supportsHttps = updated.supportsHttps;\n hasChanges = true;\n }\n if (\n updated.httpAccessible !== undefined &&\n existing.httpAccessible !== updated.httpAccessible\n ) {\n existing.httpAccessible = updated.httpAccessible;\n hasChanges = true;\n }\n\n return hasChanges ? existing : null;\n }\n\n private scheduleNextScan(): void {\n if (!this.isRunning) return;\n\n this.scanTimer = setTimeout(() => {\n this.scanTimer = null;\n if (this.isRunning) {\n this.performScan().finally(() => {\n if (this.isRunning) {\n this.scheduleNextScan();\n }\n });\n }\n }, this.options.scanIntervalMs);\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 /** PIR sensitivity (typically 1..100). Camera-side field is\n * `sensiValue` in the cmd_id 212 response. */\n sensitive?: number;\n /** False-positive reduction toggle (`reduceFalseAlarm` on the\n * wire). 0/1. */\n reduceAlarm?: number;\n /** Cooldown between consecutive PIR triggers (seconds). */\n interval?: number;\n /** Firmware-advertised max value for `interval`. Drives the\n * upper bound of operator-facing sliders. */\n intervalMax?: 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\n/**\n * Snapshot of `<VersionInfo>` returned by Baichuan cmd_id=80\n * (`BC_CMD_ID_GET_VERSION_INFO`) — the same payload the Reolink mobile app\n * displays in \"About this device\". Distinct from `getSystemGeneral`\n * (cmd_104) which holds time/locale.\n *\n * All fields optional because firmware variants and models include\n * different subsets. Empty XML elements (e.g. `<cc3200Version></cc3200Version>`)\n * come through as the empty string so callers can distinguish \"absent\"\n * (undefined) from \"explicitly empty\" (\"\").\n */\nexport interface BaichuanVersionInfo {\n /** User-assigned name (matches the OSD camera name). */\n name?: string;\n /** Model code, e.g. `\"E1 Zoom\"`, `\"RLC-810A\"`. */\n type?: string;\n /** Hardware serial number. */\n serialNumber?: string;\n /** Build identifier, typically a date string like `\"build 2503281992\"`. */\n buildDay?: string;\n /** Internal hardware revision, e.g. `\"IPC_NT14NA48MPSD6\"`. */\n hardwareVersion?: string;\n /** Config-format version Reolink uses to identify the schema the device speaks. */\n cfgVersion?: string;\n /** Firmware version, e.g. `\"v3.2.0.4741_2503281992\"`. */\n firmwareVersion?: string;\n /** Extended hardware detail (often hardwareVersion + region/sku suffix). */\n detail?: string;\n /** IE-Client identifier (legacy plug-in tag, usually `\"IEClient\"`). */\n IEClient?: string;\n /** Legacy MCU firmware version (present on some Reolink wireless models). */\n cc3200Version?: string;\n /** Signal-processor firmware version (rarely populated). */\n spVersion?: string;\n /** File extension Reolink expects for firmware updates (`\"pak\"`). */\n pakSuffix?: string;\n /** Reolink item / SKU number, e.g. `\"E340\"`. */\n itemNo?: string;\n /** AI-model bundle version (the on-device people/vehicle/animal detector). */\n aiVersion?: string;\n /** Diagnostic helper string the app sends to support, e.g. `\"blackPointsLevel=0\"`. */\n helpVersion?: 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 /** Raw XML response from GetEnc (cmd_id 56) for debugging stream availability */\n rawEncXml?: string | undefined;\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 /** Raw XML response from GetEnc (cmd_id 56) for debugging */\n rawXml?: string;\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 | \"battery\"\n | \"other\";\n\nexport interface ReolinkSimpleEvent {\n type: ReolinkSimpleEventType;\n channel: number;\n timestamp: number;\n /** Present when type === \"battery\" — pushed by the camera via cmdId 252. */\n battery?: Partial<BatteryInfo>;\n}\n\n/**\n * A single detection bounding box in normalized [0, 1] coordinates relative to\n * the source video frame. Using fractions instead of pixels keeps the same box\n * valid across mainStream / subStream / externStream output and across firmware\n * resolution changes.\n */\nexport interface ReolinkDetectionBox {\n /** Left edge in [0, 1] (0 = left, 1 = right). */\n x: number;\n /** Top edge in [0, 1] (0 = top, 1 = bottom). */\n y: number;\n /** Width in [0, 1]. */\n width: number;\n /** Height in [0, 1]. */\n height: number;\n /** AI class label if the camera reports one (e.g. \"person\", \"vehicle\"). */\n label?: string;\n /** Confidence in [0, 1] if exposed by the camera. */\n confidence?: number;\n}\n\n/**\n * Diagnostic state describing how much of the BcMedia additionalHeader the\n * decoder was able to interpret. Useful for consumers iterating on the format.\n */\nexport type ReolinkDetectionDecodeState =\n /** Header marker was missing or malformed. */\n | \"invalid-marker\"\n /** Baseline 128-byte header — camera reports no overlay metadata. */\n | \"no-overlay\"\n /** Overlay block present, but coordinates have not been decoded yet. */\n | \"overlay-undecoded\"\n /** Overlay block decoded successfully. */\n | \"overlay-decoded\";\n\n/**\n * High-level \"detection\" event emitted alongside every video frame that carries\n * a non-empty BcMedia additionalHeader. Mirrors `ReolinkSimpleEvent` but is\n * sourced from the streaming side-channel rather than from cmd_id 33 push events.\n */\nexport interface ReolinkDetectionEvent {\n channel: number;\n /** Microseconds timestamp from the BcMedia video frame. */\n microseconds: number;\n /** Stream profile that produced the underlying frame. */\n profile: \"main\" | \"sub\" | \"ext\";\n /** Boxes in [0, 1] fractional coordinates. */\n boxes: ReolinkDetectionBox[];\n /** Source frame width (from BcMedia InfoV1/V2) if known. */\n frameWidth?: number;\n /** Source frame height (from BcMedia InfoV1/V2) if known. */\n frameHeight?: number;\n /** Decoder diagnostic state. */\n decodeState: ReolinkDetectionDecodeState;\n /** Raw additionalHeader bytes — kept for downstream decoder work. */\n rawHeader: Buffer;\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 /** True when device is a doorbell (can potentially support wireless chime) or has dingDong abilities explicitly detected. */\n hasWirelessChime: 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 /** Wireless chime IDs from GetDingDongList (cmd 484) */\n dingDongListIds?: number[];\n /** Wireless chime IDs from GetDingDongCfg (cmd 486) */\n dingDongCfgIds?: number[];\n /** Error message if chime discovery failed */\n wirelessChimeError?: string;\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 * Per-stream encoding entry inside `Compression`. Captured live on\n * E1-Zoom (TCP), Doorbell (UDP), Hub channel-0 (Argus 3E).\n *\n * Note: the camera-side field name is `frame`, NOT `frameRate`.\n * `videoEncType` is a numeric enum (`0` = h264, `1` = h265). The\n * `gop`/`encoderType`/`separateCfg` sub-blocks are firmware-dependent\n * (Hub-channel cameras carry them; standalone devices may not).\n */\nexport interface CompressionStream {\n audio?: number | undefined;\n resolutionName?: string | undefined;\n width?: number | undefined;\n height?: number | undefined;\n frame?: number | undefined;\n bitRate?: number | undefined;\n encoderProfile?: string | undefined;\n videoEncType?: number | undefined;\n gop?: { cur?: number; max?: number; min?: number } | undefined;\n encoderType?: string | undefined;\n [key: string]: unknown;\n}\n\n/**\n * One allowable resolution from a `getEncOptions` reply.\n * `videoEncTypeList` enumerates the codecs supported at this resolution\n * (mapped to `\"h264\"`/`\"h265\"`).\n */\nexport interface EncResolutionOption {\n width: number;\n height: number;\n /** Codecs available at this resolution. */\n videoEncTypes: Array<\"h264\" | \"h265\">;\n /** Camera-default framerate at this resolution, if reported. */\n defaultFramerate?: number;\n /** Camera-default bitrate (kbps) at this resolution, if reported. */\n defaultBitrate?: number;\n /** Camera-default GOP at this resolution, if reported. */\n defaultGop?: number;\n /** Allowed framerate values. */\n framerateOptions: number[];\n /** Allowed bitrate values (kbps). */\n bitrateOptions: number[];\n}\n\n/**\n * Allowable values for a single stream profile (mainStream / subStream / thirdStream).\n * Aggregated from `getStreamInfoList` (cmd_146) so consumers can populate UI\n * pickers without re-implementing the parsing logic.\n */\nexport interface EncStreamOptions {\n /** Stream profile (one of `mainStream` / `subStream` / `thirdStream`). */\n type: string;\n /** Each entry is a `{width, height}` paired with its allowed values. */\n resolutions: EncResolutionOption[];\n /** Encoder rate-control modes Reolink exposes in the app. */\n encoderTypes: Array<\"vbr\" | \"cbr\">;\n /** Encoder profiles Reolink exposes in the app. */\n encoderProfiles: Array<\"high\" | \"main\" | \"baseline\">;\n}\n\n/**\n * Reply from `getEncOptions` — the set of allowable values for `setEnc`,\n * derived from `getStreamInfoList`. Use this to validate user input or\n * populate UI selectors.\n */\nexport interface EncOptions {\n channel: number;\n mainStream?: EncStreamOptions;\n subStream?: EncStreamOptions;\n thirdStream?: EncStreamOptions;\n}\n\n/**\n * Patch payload accepted by `setEnc` for a single stream block\n * (`mainStream` / `subStream` / `thirdStream`). All fields optional —\n * unspecified ones are preserved from the device's current config.\n */\nexport interface EncStreamPatch {\n audio?: 0 | 1;\n width?: number;\n height?: number;\n bitRate?: number;\n frameRate?: number;\n videoEncType?: \"h264\" | \"h265\";\n encoderType?: \"vbr\" | \"cbr\";\n encoderProfile?: \"high\" | \"main\" | \"baseline\";\n /** Keyframe interval in seconds — patches `<gop><cur>`. */\n gop?: number;\n}\n\n/**\n * Encoding configuration (getEnc response).\n * cmdId=56 (GetEnc) — payload is wrapped in `Compression`, not `Enc`.\n */\nexport interface EncConfig {\n body?: {\n Compression?: {\n channelId?: number | undefined;\n isNoTranslateFrame?: number | undefined;\n mainStream?: CompressionStream | undefined;\n subStream?: CompressionStream | undefined;\n thirdStream?: CompressionStream | undefined;\n separateCfg?: { encodeCfg?: number; [key: string]: unknown } | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * ISP / image input configuration. Both `getIsp` (cmdId=25) and\n * `getImage` (cmdId=26) return identical payloads on observed firmwares\n * — the underlying VideoInput + InputAdvanceCfg blocks. Different\n * cmdIds preserved for backwards compatibility.\n */\nexport interface IspConfig {\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 sharpen?: number | undefined;\n corridorAbility?: number | undefined;\n corridorMode?: string | undefined;\n [key: string]: unknown;\n };\n InputAdvanceCfg?: {\n channelId?: number | undefined;\n digitalChannel?: number | undefined;\n separateCfg?: { encType?: number; constantFrameRate?: number; [key: string]: unknown } | undefined;\n PowerLineFrequency?: { mode?: string; enable?: number; [key: string]: unknown } | undefined;\n Exposure?: {\n mode?: string;\n Gainctl?: { defMin?: number; defMax?: number; curMin?: number; curMax?: number };\n Shutterctl?: { defMin?: number; defMax?: number; curMin?: number; curMax?: number };\n shutterLevel?: string;\n gainLevel?: number;\n [key: string]: unknown;\n } | undefined;\n Scene?: {\n mode?: string;\n modeList?: string;\n Redgain?: { min?: number; max?: number; cur?: number };\n Bluegain?: { min?: number; max?: number; cur?: number };\n [key: string]: unknown;\n } | undefined;\n DayNight?: { mode?: string; IrcutMode?: string; Threshold?: string; [key: string]: unknown } | undefined;\n BLC?: { enable?: number; mode?: string; [key: string]: unknown } | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * IR / supplemental light state (`getIrLights`). Captured live on every\n * test camera — `state` is the operator-facing toggle, `lightState` is\n * the camera's own runtime status. `doorbellLightState` /\n * `doorbellAbility` only appear on doorbell models.\n */\nexport interface IrLightsConfig {\n body?: {\n LedState?: {\n channelId?: number | undefined;\n ledVersion?: number | undefined;\n IRLedBrightness?: number | undefined;\n state?: string | undefined;\n lightState?: string | undefined;\n doorbellLightState?: string | undefined;\n doorbellAbility?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Privacy mask configuration (`getMask`). The `Shelter` block always\n * contains `enable` + `maxNum` + `shelterList`; tracked-shelter sub-\n * blocks (`trackEnable`, `trackShelterList`) are PTZ-only and\n * conditional on the camera supporting motion-tracking. Hub-channel\n * cameras include `separateCfg` + `logicChannel`.\n */\nexport interface MaskConfig {\n body?: {\n Shelter?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n maxNum?: number | undefined;\n shelterList?: unknown;\n trackEnable?: number | undefined;\n maxTrackShelterNum?: number | undefined;\n trackShelterList?: unknown;\n separateCfg?: { shelter?: number; [key: string]: unknown } | undefined;\n logicChannel?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Audio noise reduction configuration (`getAudioNoise`).\n * cmdId=439. Note: the wire tag is lowercase `aiDenoise` (capital `V`\n * in `@_Version`). Mirrors the camera-side schema observed live.\n */\nexport interface AudioNoiseConfig {\n body?: {\n aiDenoise?: {\n channelId?: number | undefined;\n enable?: number | undefined;\n level?: number | undefined;\n [key: string]: unknown;\n };\n };\n}\n\n/**\n * Auto-focus configuration (`getAutoFocus`). cmdId=224.\n *\n * The `disable` field is a 0/1 flag — `0` means autofocus is ENABLED.\n * Non-PTZ cameras may return an empty `{}` or 400 — callers should\n * narrow `body?.AutoFocus?.disable` defensively.\n */\nexport interface AutoFocusConfig {\n body?: {\n AutoFocus?: {\n channelId?: number | undefined;\n disable?: number | 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 * 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 * 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 /** Legacy format (some firmware versions) */\n item?: Array<{\n userName?: string | undefined;\n ip?: string | undefined;\n level?: number | undefined;\n [key: string]: unknown;\n }>;\n /** Current format (OnlineUser array) */\n OnlineUser?: Array<{\n userId?: number | undefined;\n sessionId?: number | undefined;\n userName?: string | undefined;\n userLevel?: number | undefined;\n ipAddress?: string | undefined;\n macAddress?: string | undefined;\n enableOutoffLine?: number | undefined;\n isOnline?: number | 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\n// --------------------\n// Chime / DingDong types\n// --------------------\n\n/** A paired wireless chime device as returned by GetDingDongList. */\nexport interface ChimeDevice {\n id: number;\n name: string;\n /** 0 = offline, 1 = online */\n netState: number;\n}\n\n/** Wireless chime parameters returned by DingDongOpt (option 2 / getParam). */\nexport interface ChimeParams {\n name?: string;\n /** Volume level (0-4 typical) */\n volLevel?: number;\n /** LED state: 0 = off, 1 = on */\n ledState?: number;\n}\n\n/** Per-event alarm config entry inside a chime's GetDingDongCfg response. */\nexport interface ChimeAlarmCfg {\n /** Whether this event type triggers the chime: 0 = off, 1 = on */\n valid: number;\n /** Ringtone / music ID */\n musicId: number;\n}\n\n/** Per-chime config from GetDingDongCfg. */\nexport interface ChimeCfg {\n /** Chime ring ID */\n id: number;\n /** Map of event type string → alarm config */\n type: Record<string, ChimeAlarmCfg>;\n}\n\n/** Hardwired (wired-in) chime state from GetDingDongCtrl / SetDingDongCtrl. */\nexport interface HardwiredChimeState {\n /** Chime type string (e.g. \"dingdong\", \"single\", \"dual\") */\n type: string;\n /** Whether the chime is enabled */\n enabled: boolean;\n /** Duration / timing value */\n time: number;\n}\n\n/** Wireless chime silent mode state from GetDingDongSilent / SetDingDongSilent. */\nexport interface WirelessChimeSilentState {\n /** The wireless chime device ID */\n id: number;\n /**\n * Silent mode duration in seconds.\n * 0 = not silenced (chime active), >0 = silenced for this many seconds.\n */\n time: number;\n /** Whether the chime is currently active (not silenced). Derived: time === 0 */\n active: boolean;\n}\n\n// ====================================================================\n// Email / NTP / Time / DST / AutoReboot\n// All schemas derived from Reolink Client pcap (2026-05-16).\n// ====================================================================\n\n/** Image vs video attachment for motion alert emails. */\nexport type EmailAttachmentType = \"picture\" | \"video\" | \"none\";\n\n/** Whether the alert body carries the standard human-readable text. */\nexport type EmailTextType = \"withText\" | \"noText\";\n\n/**\n * Email SMTP server configuration. Returned by GetEmail (cmdId=42) and\n * accepted by SetEmail (cmdId=43) and TestEmail (cmdId=141).\n *\n * Read-only fields (only present on GET): `senderMaxLen`, `pwdMaxLen`,\n * `emailAttachAbility` — the camera-reported capability bitmap.\n */\nexport interface EmailConfig {\n smtpServer: string;\n /** Sender email address / SMTP username. Empty when unconfigured. */\n userName: string;\n /** SMTP password. Always sent in cleartext — camera limitation. */\n password: string;\n /** Recipient 1. */\n address1: string;\n /** Recipient 2 (optional). */\n address2: string;\n /** Recipient 3 (optional). */\n address3: string;\n smtpPort: number;\n sendNickname: string;\n /** 1 = attach picture/video, 0 = text-only. */\n attachment: 0 | 1;\n attachmentType: EmailAttachmentType;\n textType: EmailTextType;\n /** 1 = SSL/TLS, 0 = plain SMTP. */\n ssl: 0 | 1;\n /** Throttle between successive motion mails in seconds. Ignored on battery cams. */\n interval: number;\n // GET-only capability fields\n senderMaxLen?: number;\n pwdMaxLen?: number;\n /** Bitmap of supported attachment combinations. */\n emailAttachAbility?: number;\n}\n\n/** Patch payload accepted by SetEmail. All fields optional — only supplied ones are written. */\nexport type EmailConfigPatch = Partial<\n Omit<EmailConfig, \"senderMaxLen\" | \"pwdMaxLen\" | \"emailAttachAbility\">\n>;\n\n/**\n * Single entry in the EmailTask `typeScheduleList`. `valueTable` is a\n * 168-char string of '0'/'1' representing the 7-days × 24-hours grid.\n */\nexport interface EmailTaskScheduleItem {\n /**\n * Trigger type. Known: \"MD\" (motion), \"Normal\" (continuous record),\n * \"people\", \"vehicle\", \"dog_cat\", \"face\", \"package\", \"cry\", \"visitor\",\n * \"doorbell\", \"none\" (placeholder slot).\n * Treated as string to cover firmware-specific extensions.\n */\n type: string;\n /** 168-char 0/1 schedule bitmap. */\n valueTable: string;\n}\n\n/** Email schedule configuration (cmdId=216/217). */\nexport interface EmailTaskConfig {\n channelId: number;\n enable: 0 | 1;\n typeScheduleList: EmailTaskScheduleItem[];\n}\n\n/** NTP server configuration (cmdId=38/39). */\nexport interface NtpConfig {\n /** 1 = NTP sync enabled, 0 = disabled. */\n enable: 0 | 1;\n /** Hostname or IP of the NTP server. */\n server: string;\n /** Sync interval in minutes. */\n synchronizeInterval: number;\n /** UDP port. Default 123. */\n port: number;\n}\n\nexport type NtpConfigPatch = Partial<NtpConfig>;\n\n/** Day-of-week labels accepted by Dst and AutoReboot blocks. */\nexport type DayOfWeek =\n | \"Sunday\"\n | \"Monday\"\n | \"Tuesday\"\n | \"Wednesday\"\n | \"Thursday\"\n | \"Friday\"\n | \"Saturday\"\n | \"everyday\";\n\n/** Daylight Saving Time configuration (cmdId=106/107). */\nexport interface DstConfig {\n enable: 0 | 1;\n /** Offset in hours (typically 1). */\n offset: number;\n startMonth: number;\n /** Week-of-month index (1..5). 5 = \"last week of the month\". */\n startWeekIndex: number;\n startWeekday: DayOfWeek;\n startHour: number;\n startMinute: number;\n startSecond: number;\n endMonth: number;\n endWeekIndex: number;\n endWeekday: DayOfWeek;\n endHour: number;\n endMinute: number;\n endSecond: number;\n /** Schema version (returned by camera, defaults to 0). */\n version?: number;\n}\n\nexport type DstConfigPatch = Partial<DstConfig>;\n\n/** OSD date format. */\nexport type OsdDateFormat = \"DMY\" | \"MDY\" | \"YMD\";\n\n/** 24h (0) or 12h (1) clock format. */\nexport type TimeFormat = 0 | 1;\n\n/**\n * Full SystemGeneral block returned by cmdId=104. SET (cmdId=105) accepts a\n * subset (any combination of these fields) plus the mandatory `<year>0</year>`\n * marker that signals \"do not set the manual clock\". When you DO want to set\n * the manual clock, pass year/month/day/hour/minute/second together.\n */\nexport interface SystemGeneralConfig {\n /** Timezone offset in seconds. POSIX convention: UTC+1 → -3600. */\n timeZone: number;\n osdFormat: OsdDateFormat;\n year: number;\n month: number;\n day: number;\n hour: number;\n minute: number;\n second: number;\n deviceId: number;\n timeFormat: TimeFormat;\n language: string;\n deviceName: string;\n loginLock: 0 | 1;\n lockTime: number;\n allowedTimes: number;\n /** 1 when DST is currently active (camera-side). Read-only on most firmwares. */\n isDst: 0 | 1;\n}\n\n/**\n * Patch accepted by SetSystemGeneral. When `manualTime` is provided, the\n * year/month/day/hour/minute/second fields are sent as-is. When omitted, the\n * builder injects `<year>0</year>` to skip the manual clock even when other\n * fields are present. Setting only `deviceName` triggers the\n * `<deviceNameOnly>1</deviceNameOnly>` flag automatically.\n */\nexport interface SystemGeneralPatch {\n timeZone?: number;\n osdFormat?: OsdDateFormat;\n timeFormat?: TimeFormat;\n language?: string;\n deviceName?: string;\n loginLock?: 0 | 1;\n lockTime?: number;\n allowedTimes?: number;\n manualTime?: {\n year: number;\n month: number;\n day: number;\n hour: number;\n minute: number;\n second: number;\n };\n}\n\n/** AutoReboot schedule (cmdId=100/101). */\nexport interface AutoRebootConfig {\n enable: 0 | 1;\n weekDay: DayOfWeek;\n hour: number;\n minute: number;\n second: number;\n}\n\nexport type AutoRebootConfigPatch = Partial<AutoRebootConfig>;\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(adtsFrame: Buffer): {\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 videoFramesSinceAbsUsChange = 0;\n private videoEstimatedFrameDeltaUs: 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 this.videoFramesSinceAbsUsChange = 0;\n this.videoEstimatedFrameDeltaUs = 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 lastAbsUs = this.videoLastAbsUs;\n const deltaUs = absUs - lastAbsUs;\n this.videoLastAbsUs = absUs;\n\n // Some camera models produce a coarse or repeated timestamp (e.g., same absUs for N frames\n // then jump by a larger delta). If we treat repeated timestamps as untrusted and use an EMA\n // of the coarse deltas, we end up advancing RTP timestamps too slowly (stutter/jitter).\n //\n // Heuristic:\n // - If absUs did not advance, use an estimated per-frame delta (learned from the next advance)\n // or fallback fps.\n // - If absUs advanced after a streak of repeats, assume the delta covers (streak+1) frames and\n // derive a per-frame delta.\n let effectiveDeltaUs: number;\n if (!Number.isFinite(deltaUs) || deltaUs < 0) {\n this.logVideoTiming(\n \"untrusted-delta\",\n `discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${lastAbsUs} avgDeltaUs=${this.videoAvgDeltaUs ?? \"n/a\"}); using fallback`,\n );\n this.videoFramesSinceAbsUsChange = 0;\n effectiveDeltaUs =\n this.videoEstimatedFrameDeltaUs ?? this.fallbackVideoDeltaUs;\n } else if (deltaUs === 0) {\n this.videoFramesSinceAbsUsChange++;\n // Keep the average stable; do NOT update EMA with zeros.\n effectiveDeltaUs =\n this.videoEstimatedFrameDeltaUs ?? this.fallbackVideoDeltaUs;\n } else if (deltaUs <= this.maxTrustedDeltaUs) {\n if (this.videoFramesSinceAbsUsChange > 0) {\n const framesCovered = this.videoFramesSinceAbsUsChange + 1;\n const perFrameDeltaUs = Math.max(\n 1,\n Math.round(deltaUs / framesCovered),\n );\n this.videoEstimatedFrameDeltaUs = perFrameDeltaUs;\n this.videoFramesSinceAbsUsChange = 0;\n\n const prevAvg = this.videoAvgDeltaUs ?? perFrameDeltaUs;\n this.videoAvgDeltaUs =\n prevAvg + (perFrameDeltaUs - prevAvg) * this.emaAlpha;\n effectiveDeltaUs = perFrameDeltaUs;\n } else {\n const prevAvg = this.videoAvgDeltaUs ?? deltaUs;\n this.videoAvgDeltaUs = prevAvg + (deltaUs - prevAvg) * this.emaAlpha;\n effectiveDeltaUs = deltaUs;\n }\n } else {\n // Large forward jump: do not incorporate into EMA.\n this.logVideoTiming(\n \"untrusted-delta\",\n `discarded deltaUs=${deltaUs} (absUs=${absUs} lastAbsUs=${lastAbsUs} avgDeltaUs=${this.videoAvgDeltaUs ?? \"n/a\"} sameCount=${this.videoFramesSinceAbsUsChange}); using fallback`,\n );\n this.videoFramesSinceAbsUsChange = 0;\n effectiveDeltaUs =\n this.videoEstimatedFrameDeltaUs ?? 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 sharedProcessId =\n typeof process !== \"undefined\" && typeof process.pid === \"number\"\n ? process.pid\n : \"n/a\";\n const sharedInstanceId = (() => {\n try {\n const g = globalThis as any;\n const k = \"__nodelink_rfc4571_shared_instance_id__\";\n if (!g[k]) {\n g[k] = Math.random().toString(16).slice(2);\n }\n return String(g[k]);\n } catch {\n return \"n/a\";\n }\n })();\n\n const sharedLog = (message: string) => {\n try {\n const logger: any = options.logger;\n const prefix = `[native-rfc4571 shared pid=${sharedProcessId} inst=${sharedInstanceId}]`;\n // Scrypted frequently suppresses debug logs; prefer info/log so traces are visible.\n if (logger?.info) logger.info(`${prefix} ${message}`);\n else if (logger?.log) logger.log(`${prefix} ${message}`);\n else if (logger?.debug) logger.debug(`${prefix} ${message}`);\n } catch {\n // ignore\n }\n };\n\n // For dedicated sockets (deviceId), idleTeardownMs MUST NOT be 0.\n // If the RFC4571 server never tears down, the dedicated Baichuan session is never\n // released and the device accumulates active sessions over time.\n const normalizeDedicatedIdleTeardownMs = (ms: number | undefined): number => {\n if (typeof ms === \"number\" && Number.isFinite(ms) && ms > 0) return ms;\n return 15_000;\n };\n\n // When deviceId is provided, callers may request the *same* native stream multiple\n // times using different external identifiers. That can lead to duplicate RFC4571\n // servers and therefore duplicate dedicated Baichuan sessions.\n //\n // To prevent this, we share a single RFC4571 server per stable stream identity.\n // Each caller gets a ref-counted handle; the underlying server is closed only when\n // all handles are released.\n const sharedKey = buildSharedRfc4571Key(options);\n if (!sharedKey) {\n sharedLog(\n \"disabled (no deviceId); using unshared RFC4571 server for this request\",\n );\n return await createRfc4571TcpServerInternal(options);\n }\n\n sharedLog(\n `request key=${formatSharedKeyForLog(sharedKey)} (requestedId=${options.requestedId ?? \"n/a\"})`,\n );\n\n const existing = sharedRfc4571Servers.get(sharedKey);\n if (existing && existing.server.server.listening) {\n sharedLog(\n `reuse existing host=${existing.server.host} port=${existing.server.port} refCount=${existing.refCount}`,\n );\n return acquireSharedRfc4571Handle(sharedKey, existing, sharedLog);\n }\n if (existing) {\n // Stale entry.\n sharedLog(\n `stale entry found; dropping (listening=${existing.server.server.listening})`,\n );\n sharedRfc4571Servers.delete(sharedKey);\n }\n\n const inFlight = sharedRfc4571Creates.get(sharedKey);\n if (inFlight) {\n sharedLog(\"awaiting in-flight create\");\n await inFlight;\n const created = sharedRfc4571Servers.get(sharedKey);\n if (created) {\n sharedLog(\n `acquiring after in-flight create host=${created.server.host} port=${created.server.port} refCount=${created.refCount}`,\n );\n return acquireSharedRfc4571Handle(sharedKey, created, sharedLog);\n }\n // Fallback: avoid deadlocks.\n sharedLog(\n \"in-flight create completed but no entry found; falling back to unshared\",\n );\n return await createRfc4571TcpServerInternal(options);\n }\n\n const createPromise = (async () => {\n const idleTeardownMs = normalizeDedicatedIdleTeardownMs(\n options.idleTeardownMs,\n );\n sharedLog(\n `creating new shared RFC4571 server (idleTeardownMs=${idleTeardownMs})`,\n );\n const server = await createRfc4571TcpServerInternal({\n ...options,\n // Force a sane idle teardown for dedicated sessions so sockets are released.\n idleTeardownMs,\n });\n\n const entry: SharedRfc4571Entry = {\n server,\n refCount: 0,\n };\n sharedRfc4571Servers.set(sharedKey, entry);\n sharedLog(`created host=${server.host} port=${server.port}`);\n\n // Best-effort cleanup if the underlying server tears down itself (errors, watchdog).\n server.server.once(\"close\", () => {\n const current = sharedRfc4571Servers.get(sharedKey);\n if (current?.server === server) {\n sharedLog(\"underlying server closed; evicting shared cache entry\");\n sharedRfc4571Servers.delete(sharedKey);\n }\n });\n })().finally(() => {\n sharedRfc4571Creates.delete(sharedKey);\n });\n\n sharedRfc4571Creates.set(sharedKey, createPromise);\n await createPromise;\n\n const created = sharedRfc4571Servers.get(sharedKey);\n if (created) return acquireSharedRfc4571Handle(sharedKey, created, sharedLog);\n return await createRfc4571TcpServerInternal(options);\n}\n\ninterface SharedRfc4571Entry {\n server: Rfc4571TcpServer;\n refCount: number;\n}\n\nconst sharedRfc4571Servers = new Map<string, SharedRfc4571Entry>();\nconst sharedRfc4571Creates = new Map<string, Promise<void>>();\n\nconst buildSharedRfc4571Key = (\n options: Rfc4571TcpServerOptions,\n): string | undefined => {\n // Only share when deviceId is provided (dedicated sockets).\n if (!options.deviceId) return;\n\n // This key intentionally ignores external identifiers like requestedId.\n // It captures the stream identity that maps to a single Baichuan live stream.\n const channelKey =\n options.channel === undefined ? \"composite\" : String(options.channel);\n const variantKey = options.variant ?? \"default\";\n\n // IMPORTANT: Do NOT include RFC4571 client auth credentials in the key.\n // Callers may generate a new username/password on each request. If we include them,\n // the key becomes unstable and sharing never happens.\n // The shared server will return its own username/password to the caller.\n const requireAuth = Boolean(options.requireAuth);\n\n // Include payload types because they affect SDP.\n const videoPayloadType = options.videoPayloadType ?? 96;\n const audioPayloadType = options.audioPayloadType ?? 97;\n\n return [\n \"rfc4571\",\n `device:${options.deviceId}`,\n `ch:${channelKey}`,\n `profile:${options.profile}`,\n `variant:${variantKey}`,\n `auth:${requireAuth ? \"1\" : \"0\"}`,\n `vpt:${videoPayloadType}`,\n `apt:${audioPayloadType}`,\n ].join(\"|\");\n};\n\nconst formatSharedKeyForLog = (key: string): string => {\n // Redact any unexpected secrets if the format changes in the future.\n // Current key format doesn't include passwords, but keep this defensive.\n return key.replace(/\\|pass:[^|]*/g, \"|pass:<redacted>\");\n};\n\nconst acquireSharedRfc4571Handle = (\n key: string,\n entry: SharedRfc4571Entry,\n sharedLog?: (message: string) => void,\n): Rfc4571TcpServer => {\n entry.refCount++;\n sharedLog?.(`acquire handle key=${key} refCount=${entry.refCount}`);\n\n let released = false;\n const serverRef = entry.server;\n\n const close = async (reason?: unknown): Promise<void> => {\n if (released) return;\n released = true;\n\n const current = sharedRfc4571Servers.get(key);\n // If the server was already replaced/removed, do not interfere.\n if (!current || current.server !== serverRef) return;\n\n current.refCount = Math.max(0, current.refCount - 1);\n sharedLog?.(\n `release handle key=${key} refCount=${current.refCount} reason=${String(\n (reason as any)?.message ?? reason ?? \"n/a\",\n )}`,\n );\n if (current.refCount > 0) return;\n\n sharedRfc4571Servers.delete(key);\n sharedLog?.(\"refCount reached 0; closing underlying server\");\n await serverRef.close(reason ?? \"shared handle released\");\n };\n\n // Provide a handle that shares the underlying server but has an independent close().\n return {\n ...serverRef,\n close,\n };\n};\n\nasync function createRfc4571TcpServerInternal(\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 // If the camera already rejected the stream request during start()\n // (e.g. response_code 400), the error was stashed because no listener\n // existed yet. Consume it now to fail fast instead of waiting for the\n // full keyframe timeout.\n const pendingErr = (\n videoStream as BaichuanVideoStream\n ).consumePendingStartupError?.();\n if (pendingErr) {\n cleanup();\n reject(pendingErr);\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 // Release dedicated session if one was created (prevents session leak).\n if (dedicatedSession) {\n try {\n await dedicatedSession.release();\n } catch {\n // ignore\n }\n }\n\n // When a dedicated session was used, the baseApi is shared with other streams\n // (events, main/sub). Closing or idle-disconnecting it here would cascade-kill\n // unrelated streams. Only release the dedicated session (above) and leave the\n // shared API untouched.\n if (!dedicatedSession) {\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\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 // When a dedicated session was used, the baseApi is shared with other streams\n // (events, main/sub). Closing it here would cascade-kill unrelated streams.\n // Only close APIs when no dedicated session was used (API is owned by this server).\n if (closeApiOnTeardown && !dedicatedSession) {\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 * Go2rtcTcpServer — Lightweight TCP server that feeds raw Annex-B video\n * (H.264 / H.265) and ADTS AAC audio frames to go2rtc via a plain TCP\n * connection.\n *\n * go2rtc configuration example:\n * streams:\n * camera_main: tcp://127.0.0.1:{port}\n *\n * go2rtc auto-detects the codec from the bitstream (SPS/PPS/VPS NALUs).\n *\n * Lifecycle:\n * 1. Server starts listening immediately on the configured port.\n * 2. When the first TCP client connects the native camera stream is started.\n * 3. Frames are fan-out to every connected client (go2rtc typically opens\n * one connection, but multiple are supported).\n * 4. When the last client disconnects a grace period runs before stopping\n * the native stream (avoids thrashing on rapid reconnects).\n * 5. A prebuffer ring keeps the last few seconds of frames so new clients\n * get IDR-aligned fast startup without waiting for the next keyframe.\n */\n\nimport { EventEmitter } from \"node:events\";\nimport * as net from \"node:net\";\nimport type { StreamProfile } from \"../../reolink/baichuan/types\";\nimport type { ReolinkBaichuanApi } from \"../../reolink/baichuan/ReolinkBaichuanApi\";\nimport type { NativeVideoStreamVariant } from \"../../reolink/baichuan/types\";\nimport type { Logger } from \"../../debug/DebugConfig\";\nimport { createNativeStream } from \"../../rfc/helpers\";\nimport { convertToAnnexB as convertH264ToAnnexB } from \"./H264Converter\";\nimport {\n convertToAnnexB as convertH265ToAnnexB,\n isH265Irap,\n splitAnnexBToNalPayloads,\n} from \"./H265Converter\";\nimport { MpegTsMuxer } from \"./MpegTsMuxer\";\n\n// ---------------------------------------------------------------------------\n// Bounded queue (same as BaichuanRtspServer, kept local to avoid coupling)\n// ---------------------------------------------------------------------------\n\nclass AsyncBoundedQueue<T> {\n private readonly maxItems: number;\n private readonly queue: T[] = [];\n private waiting:\n | { resolve: (r: IteratorResult<T>) => void }\n | undefined;\n private closed = false;\n\n constructor(maxItems: number) {\n this.maxItems = Math.max(1, maxItems | 0);\n }\n\n push(item: T): void {\n if (this.closed) return;\n if (this.waiting) {\n const { resolve } = this.waiting;\n this.waiting = undefined;\n resolve({ value: item, done: false });\n return;\n }\n this.queue.push(item);\n if (this.queue.length > this.maxItems) {\n this.queue.splice(0, this.queue.length - this.maxItems);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n if (this.waiting) {\n const { resolve } = this.waiting;\n this.waiting = undefined;\n resolve({ value: undefined as any, done: true });\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n if (this.closed) return { value: undefined as any, done: true };\n const item = this.queue.shift();\n if (item !== undefined) return { value: item, done: false };\n return await new Promise<IteratorResult<T>>((resolve) => {\n this.waiting = { resolve };\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// NativeStreamFanout (same pattern as BaichuanRtspServer)\n// ---------------------------------------------------------------------------\n\ntype NativeFrame = {\n audio: boolean;\n data: Buffer;\n codec: string | null;\n sampleRate: number | null;\n microseconds: number | null;\n videoType?: \"H264\" | \"H265\";\n isKeyframe?: boolean;\n};\n\ntype FanoutOptions = {\n maxQueueItems: number;\n createSource: () => AsyncGenerator<NativeFrame, void, unknown>;\n onFrame?: (frame: NativeFrame) => void;\n onError?: (error: unknown) => void;\n onEnd?: () => void;\n};\n\nclass NativeStreamFanout {\n private readonly opts: FanoutOptions;\n private readonly queues = new Map<string, AsyncBoundedQueue<NativeFrame>>();\n private source: AsyncGenerator<NativeFrame, void, unknown> | null = null;\n private running = false;\n private pumpPromise: Promise<void> | null = null;\n\n constructor(opts: FanoutOptions) {\n this.opts = opts;\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n this.source = this.opts.createSource();\n\n this.pumpPromise = (async () => {\n try {\n for await (const frame of this.source!) {\n try {\n this.opts.onFrame?.(frame);\n } catch {\n // ignore observer errors\n }\n for (const q of this.queues.values()) {\n q.push(frame);\n }\n }\n } catch (e) {\n this.opts.onError?.(e);\n } finally {\n for (const q of this.queues.values()) q.close();\n this.queues.clear();\n this.running = false;\n this.opts.onEnd?.();\n }\n })();\n }\n\n subscribe(id: string): AsyncGenerator<NativeFrame, void, unknown> {\n const q = new AsyncBoundedQueue<NativeFrame>(this.opts.maxQueueItems);\n this.queues.set(id, q);\n const self = this;\n return (async function* () {\n try {\n while (true) {\n const r = await q.next();\n if (r.done) return;\n yield r.value;\n }\n } finally {\n q.close();\n self.queues.delete(id);\n }\n })();\n }\n\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n const src = this.source;\n this.source = null;\n for (const q of this.queues.values()) q.close();\n this.queues.clear();\n try {\n await src?.return(undefined as any);\n } catch {\n // ignore\n }\n try {\n await this.pumpPromise;\n } catch {\n // ignore\n }\n this.pumpPromise = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Prebuffer entry\n// ---------------------------------------------------------------------------\n\ninterface PrebufferEntry {\n /** Annex-B (video) or raw ADTS (audio) — per-client MPEG-TS muxing happens in feedClient. */\n data: Buffer;\n /** Wallclock ms (for prebuffer trimming). */\n time: number;\n isKeyframe: boolean;\n audio: boolean;\n /** Camera presentation timestamp in microseconds (for MPEG-TS PTS). */\n pts: number;\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface Go2rtcTcpServerOptions {\n /** API instance (required). */\n api: ReolinkBaichuanApi;\n /** Channel number (required). */\n channel: number;\n /** Stream profile (required). */\n profile: StreamProfile;\n /** TrackMix tele/autotrack variant. */\n variant?: NativeVideoStreamVariant;\n /** Host to listen on (default: \"127.0.0.1\"). */\n listenHost?: string;\n /** Port to listen on (default: 0 = OS picks). */\n listenPort?: number;\n /** Logger. */\n logger?: Logger;\n /**\n * External identifier for dedicated socket session.\n * When provided, a dedicated BaichuanClient is created for the stream,\n * isolating it from other streams on the shared socket.\n */\n deviceId?: string;\n /** Grace period in ms before stopping native stream after last client disconnects (default: 30 000). */\n gracePeriodMs?: number;\n /** Maximum prebuffer window in ms (default: 3 000). */\n prebufferMs?: number;\n /** Maximum write buffer per client before dropping the connection (default: 100 MB). */\n maxBufferBytes?: number;\n /**\n * Stream inactivity timeout in ms. If no frames arrive from the native\n * stream for this duration, the stream is considered dead and will be\n * torn down + restarted (similar to go2rtc's per-packet read deadline).\n * Default: 15 000 (15s). Set to 0 to disable.\n */\n streamTimeoutMs?: number;\n /**\n * When true, the native camera stream is started immediately on start()\n * rather than waiting for the first TCP client. This ensures frames are\n * already flowing (and the prebuffer is warm) when go2rtc connects.\n * Default: true.\n */\n prestartStream?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Go2rtcTcpServer\n// ---------------------------------------------------------------------------\n\nexport class Go2rtcTcpServer extends EventEmitter<{\n client: [string];\n clientDisconnected: [string];\n error: [Error];\n close: [];\n listening: [{ host: string; port: number }];\n}> {\n private readonly api: ReolinkBaichuanApi;\n private readonly channel: number;\n private readonly profile: StreamProfile;\n private readonly variant: NativeVideoStreamVariant;\n private readonly listenHost: string;\n private readonly listenPort: number;\n private readonly logger: Logger;\n private readonly deviceId: string | undefined;\n private readonly gracePeriodMs: number;\n private readonly prebufferMaxMs: number;\n private readonly maxBufferBytes: number;\n private readonly streamTimeoutMs: number;\n private readonly prestartStream: boolean;\n\n private active = false;\n private server: net.Server | undefined;\n private resolvedPort: number | undefined;\n\n // Native stream\n private nativeFanout: NativeStreamFanout | null = null;\n private nativeStreamActive = false;\n // Set only by stopNativeStream() (explicit teardown) so the fanout's onEnd\n // callback can short-circuit cleanup/restart logic. NOT set by the inactivity-\n // timeout force-restart path — that flow wants onEnd to run and decide\n // whether to restart based on prestartStream / connected clients.\n private nativeStreamStopping = false;\n // Pending retry timer for the unbounded auto-restart loop. When a stream\n // start fails transiently (camera in maintenance reboot, idle-disconnect\n // race, etc.) we keep trying with exponential backoff until either the\n // server is stopped or a frame finally arrives.\n private nativeStreamRetryTimer: ReturnType<typeof setTimeout> | undefined;\n private nativeStreamRetryDelayMs = 0;\n private dedicatedSessionRelease: (() => Promise<void>) | undefined;\n private detectedVideoType: \"H264\" | \"H265\" | undefined;\n\n // Client tracking\n private connectedClients = new Set<string>();\n private clientSockets = new Map<string, net.Socket>();\n private stopGraceTimer: ReturnType<typeof setTimeout> | undefined;\n\n // Stream health monitoring\n private lastFrameAt = 0;\n private streamHealthTimer: ReturnType<typeof setInterval> | undefined;\n private totalFramesReceived = 0;\n private totalVideoFramesWritten = 0;\n\n // Prebuffer\n private prebuffer: PrebufferEntry[] = [];\n\n // Audio metadata — populated on first valid ADTS AAC frame.\n // Exposed via getAudioInfo() for the stream-diagnostics feature.\n private audioInfo: {\n codec: \"aac-adts\";\n sampleRate: number;\n channels: number;\n configHex: string;\n } | null = null;\n\n constructor(options: Go2rtcTcpServerOptions) {\n super();\n this.api = options.api;\n this.channel = options.channel;\n this.profile = options.profile;\n this.variant = options.variant ?? \"default\";\n this.listenHost = options.listenHost ?? \"127.0.0.1\";\n this.listenPort = options.listenPort ?? 0;\n this.logger = options.logger ?? console;\n this.deviceId = options.deviceId;\n this.gracePeriodMs = options.gracePeriodMs ?? 30_000;\n this.prebufferMaxMs = options.prebufferMs ?? 3_000;\n this.maxBufferBytes = options.maxBufferBytes ?? 100_000_000;\n this.streamTimeoutMs = options.streamTimeoutMs ?? 15_000;\n this.prestartStream = options.prestartStream ?? true;\n }\n\n // -----------------------------------------------------------------------\n // Public API\n // -----------------------------------------------------------------------\n\n /** Start listening. Resolves once the TCP server is bound. */\n async start(): Promise<void> {\n if (this.active) return;\n this.active = true;\n\n this.server = net.createServer((socket) => this.handleClient(socket));\n this.server.on(\"error\", (err) => {\n this.logger.error?.(`[Go2rtcTcpServer] server error: ${err.message}`);\n this.emit(\"error\", err);\n });\n\n await new Promise<void>((resolve, reject) => {\n this.server!.listen(this.listenPort, this.listenHost, () => {\n const addr = this.server!.address() as net.AddressInfo;\n this.resolvedPort = addr.port;\n this.logger.info?.(\n `[Go2rtcTcpServer] listening on ${addr.address}:${addr.port} channel=${this.channel} profile=${this.profile}`,\n );\n this.emit(\"listening\", { host: addr.address, port: addr.port });\n resolve();\n });\n this.server!.once(\"error\", reject);\n });\n\n // Pre-start the native camera stream so the prebuffer is warm when go2rtc\n // connects. Without this, go2rtc connects → sees no data → disconnects.\n if (this.prestartStream) {\n this.logger.info?.(\n `[Go2rtcTcpServer] pre-starting native stream channel=${this.channel} profile=${this.profile}`,\n );\n this.startNativeStream();\n }\n }\n\n /** Stop the server and all active streams. */\n async stop(): Promise<void> {\n if (!this.active) return;\n this.active = false;\n clearTimeout(this.stopGraceTimer);\n this.clearNativeStreamRetry();\n this.stopStreamHealthMonitor();\n\n // Close all client sockets\n for (const [id, sock] of this.clientSockets) {\n sock.destroy();\n this.connectedClients.delete(id);\n }\n this.clientSockets.clear();\n\n // Stop native stream\n await this.stopNativeStream();\n\n // Close TCP server\n if (this.server) {\n await new Promise<void>((resolve) => {\n this.server!.close(() => resolve());\n });\n this.server = undefined;\n }\n\n this.prebuffer = [];\n this.resolvedPort = undefined;\n this.emit(\"close\");\n }\n\n /** The actual port the server is listening on (available after start()). */\n get port(): number | undefined {\n return this.resolvedPort;\n }\n\n /** The go2rtc-compatible source URL. */\n get go2rtcSourceUrl(): string | undefined {\n if (this.resolvedPort == null) return undefined;\n return `tcp://127.0.0.1:${this.resolvedPort}`;\n }\n\n /** Number of currently connected clients. */\n get clientCount(): number {\n return this.connectedClients.size;\n }\n\n // -----------------------------------------------------------------------\n // Diagnostic subscription API (implements DiagnosticStreamServer)\n //\n // Matches the shape of BaichuanRtspServer's diagnostic API so the\n // stream-diagnostic feature in the Manager app can drive either backend\n // with identical code.\n // -----------------------------------------------------------------------\n\n /**\n * Subscribe to the raw native stream for diagnostic purposes.\n * The subscriber receives the same frames the MPEG-TS muxer consumes\n * (pre-muxing). Counts as a \"consumer\" so the native stream is kept alive\n * for the lifetime of the subscription. If the stream is not already\n * running (battery camera, prestart=false), this starts it.\n */\n async subscribeDiagnostic(\n id: string,\n ): Promise<AsyncGenerator<NativeFrame, void, unknown>> {\n this.connectedClients.add(`diag:${id}`);\n if (!this.nativeStreamActive) {\n await this.startNativeStream();\n }\n if (!this.nativeFanout) {\n this.connectedClients.delete(`diag:${id}`);\n throw new Error(\n \"Go2rtcTcpServer: native stream failed to start — cannot subscribe diagnostic\",\n );\n }\n return this.nativeFanout.subscribe(`diag:${id}`);\n }\n\n /** Unsubscribe a diagnostic session and release its consumer slot. */\n unsubscribeDiagnostic(id: string): void {\n this.removeClient(`diag:${id}`, \"diagnostic unsubscribe\");\n }\n\n /**\n * Returns ADTS AAC audio metadata detected from the native stream, or\n * null if no audio frame has been observed yet (e.g. video-only cameras\n * or before the first audio packet arrives).\n */\n getAudioInfo(): {\n codec: \"aac-adts\";\n sampleRate: number;\n channels: number;\n configHex: string;\n } | null {\n return this.audioInfo;\n }\n\n // -----------------------------------------------------------------------\n // Client handling\n // -----------------------------------------------------------------------\n\n private handleClient(socket: net.Socket): void {\n const clientId = `${socket.remoteAddress}:${socket.remotePort}`;\n socket.setNoDelay(true);\n\n this.connectedClients.add(clientId);\n this.clientSockets.set(clientId, socket);\n this.logger.info?.(\n `[Go2rtcTcpServer] client connected id=${clientId} total=${this.connectedClients.size}`,\n );\n this.emit(\"client\", clientId);\n\n // Cancel any pending grace-period stop\n if (this.stopGraceTimer) {\n clearTimeout(this.stopGraceTimer);\n this.stopGraceTimer = undefined;\n }\n\n // Ensure native stream is running. Every TCP connection from go2rtc\n // represents a real downstream consumer (go2rtc 1.9.x opens the producer\n // connection on-demand when a client subscribes to the stream — it does\n // not probe idle sources). Starting the native stream here will wake a\n // sleeping battery camera.\n if (!this.nativeStreamActive) {\n this.startNativeStream();\n }\n\n // Feed this client\n this.feedClient(clientId, socket).catch((err) => {\n this.logger.warn?.(\n `[Go2rtcTcpServer] feedClient error id=${clientId}: ${err}`,\n );\n });\n\n const cleanup = (reason?: string) => {\n this.removeClient(clientId, reason);\n socket.destroy();\n };\n socket.on(\"error\", (err) => cleanup(`error: ${err.message}`));\n socket.on(\"close\", (hadError) => cleanup(hadError ? \"close (with error)\" : \"close (clean)\"));\n }\n\n private async feedClient(clientId: string, socket: net.Socket): Promise<void> {\n // Wait for the fanout to be ready — for on-demand (battery) cameras this\n // may take several seconds while the camera wakes up and the stream starts.\n const fanoutDeadline = Date.now() + 30_000;\n while (this.active && !this.nativeFanout) {\n if (socket.destroyed) return;\n if (Date.now() > fanoutDeadline) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] fanout not ready after 30s, dropping client ${clientId}`,\n );\n return;\n }\n await new Promise((r) => setTimeout(r, 100));\n }\n if (!this.active || !this.nativeFanout) return;\n\n const subscription = this.nativeFanout.subscribe(clientId);\n\n // Per-client MPEG-TS muxer — created on first video keyframe so we know\n // the video codec (H264/H265) and the muxer emits the correct PMT stream type.\n let muxer: MpegTsMuxer | null = null;\n\n // ---- Prebuffer replay: send from last video IDR onwards ----\n const prebufferSnap = this.prebuffer.slice();\n let lastIdrIdx = -1;\n for (let i = prebufferSnap.length - 1; i >= 0; i--) {\n if (prebufferSnap[i]!.isKeyframe) {\n lastIdrIdx = i;\n break;\n }\n }\n\n if (lastIdrIdx >= 0) {\n const replay = prebufferSnap.slice(lastIdrIdx);\n this.logger.info?.(\n `[Go2rtcTcpServer] prebuffer replay client=${clientId} frames=${replay.length}`,\n );\n // Initialize muxer from the first (IDR) video frame in the prebuffer\n if (!muxer) {\n muxer = new MpegTsMuxer({\n videoType: this.detectedVideoType ?? \"H264\",\n includeAudio: true,\n });\n }\n for (const entry of replay) {\n if (socket.destroyed) return;\n let ts: Buffer;\n if (!entry.audio) {\n ts = muxer.muxVideo(entry.data, entry.pts, entry.isKeyframe);\n } else {\n ts = muxer.muxAudio(entry.data, entry.pts);\n }\n if (ts.length > 0) socket.write(ts);\n }\n }\n\n // ---- Live frames ----\n let seenKeyframe = lastIdrIdx >= 0;\n let liveFrameCount = 0;\n let liveVideoWritten = 0;\n let lastLogAt = Date.now();\n try {\n this.logger.info?.(\n `[Go2rtcTcpServer] entering live loop client=${clientId} seenKeyframe=${seenKeyframe}`,\n );\n for await (const frame of subscription) {\n if (socket.destroyed || !this.active) {\n this.logger.info?.(\n `[Go2rtcTcpServer] live loop exit client=${clientId} destroyed=${socket.destroyed} active=${this.active}`,\n );\n break;\n }\n\n liveFrameCount++;\n\n if (frame.audio) {\n // Audio: only write after muxer is ready (i.e. after first video keyframe)\n if (muxer) {\n const pts = frame.microseconds ?? Date.now() * 1000;\n const ts = muxer.muxAudio(frame.data, pts);\n if (ts.length > 0) socket.write(ts);\n }\n continue;\n }\n\n // Video frame — convert to Annex-B\n const annexB = this.convertVideoFrame(frame);\n if (!annexB) continue;\n\n const isKf = this.isAnnexBKeyframe(annexB, frame.videoType);\n\n // Gate on first keyframe so go2rtc doesn't get orphan P-frames\n if (!seenKeyframe) {\n if (!isKf) continue;\n seenKeyframe = true;\n this.logger.info?.(\n `[Go2rtcTcpServer] first live keyframe client=${clientId} after ${liveFrameCount} frames`,\n );\n // Initialize muxer now that we know the codec from the actual frame\n if (!muxer) {\n muxer = new MpegTsMuxer({\n videoType: frame.videoType ?? this.detectedVideoType ?? \"H264\",\n includeAudio: true,\n });\n }\n }\n\n const pts = frame.microseconds ?? Date.now() * 1000;\n const ts = muxer!.muxVideo(annexB, pts, isKf);\n socket.write(ts);\n\n liveVideoWritten++;\n this.totalVideoFramesWritten++;\n\n // Periodic log every 10 seconds\n if (Date.now() - lastLogAt > 10_000) {\n this.logger.info?.(\n `[Go2rtcTcpServer] live stats client=${clientId} received=${liveFrameCount} written=${liveVideoWritten} bufLen=${socket.writableLength}`,\n );\n lastLogAt = Date.now();\n }\n\n // Backpressure: drop client if buffer grows too large\n if (socket.writableLength > this.maxBufferBytes) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] buffer overflow (${socket.writableLength} bytes), dropping client ${clientId}`,\n );\n socket.destroy();\n break;\n }\n }\n this.logger.info?.(\n `[Go2rtcTcpServer] live loop ended naturally client=${clientId} received=${liveFrameCount} written=${liveVideoWritten}`,\n );\n } finally {\n await subscription.return(undefined as any).catch(() => {});\n }\n }\n\n // -----------------------------------------------------------------------\n // Frame conversion\n // -----------------------------------------------------------------------\n\n /**\n * Convert a native video frame to Annex-B.\n * Returns null for audio frames (handled separately by muxAudio).\n */\n private convertVideoFrame(frame: NativeFrame): Buffer | null {\n if (frame.audio) return null;\n if (frame.data.length === 0) return null;\n try {\n if (frame.videoType === \"H264\") {\n return convertH264ToAnnexB(frame.data);\n }\n if (frame.videoType === \"H265\") {\n return convertH265ToAnnexB(frame.data);\n }\n } catch {\n // ignore conversion errors\n }\n // Unknown codec — pass raw data through\n return frame.data;\n }\n\n /** Check if an Annex-B buffer contains a keyframe (IDR for H.264, IRAP for H.265). */\n private isAnnexBKeyframe(\n annexB: Buffer,\n videoType?: \"H264\" | \"H265\",\n ): boolean {\n try {\n if (videoType === \"H264\") {\n const nals = Go2rtcTcpServer.splitAnnexBNals(annexB);\n return nals.some((n) => n.length >= 1 && (n[0]! & 0x1f) === 5);\n }\n if (videoType === \"H265\") {\n const nals = splitAnnexBToNalPayloads(annexB);\n return nals.some(\n (n) => n.length >= 2 && isH265Irap((n[0]! >> 1) & 0x3f),\n );\n }\n } catch {\n // ignore\n }\n return false;\n }\n\n /** Split Annex-B byte stream into individual NAL units. */\n private static splitAnnexBNals(buf: Buffer): Buffer[] {\n const nals: Buffer[] = [];\n let i = 0;\n while (i < buf.length) {\n // Find start code (0x000001 or 0x00000001)\n if (\n i + 2 < buf.length &&\n buf[i] === 0 &&\n buf[i + 1] === 0\n ) {\n let scLen: number;\n if (buf[i + 2] === 1) {\n scLen = 3;\n } else if (i + 3 < buf.length && buf[i + 2] === 0 && buf[i + 3] === 1) {\n scLen = 4;\n } else {\n i++;\n continue;\n }\n // Find next start code\n const nalStart = i + scLen;\n let nalEnd = buf.length;\n for (let j = nalStart; j < buf.length - 2; j++) {\n if (\n buf[j] === 0 &&\n buf[j + 1] === 0 &&\n (buf[j + 2] === 1 || (j + 3 < buf.length && buf[j + 2] === 0 && buf[j + 3] === 1))\n ) {\n nalEnd = j;\n break;\n }\n }\n if (nalEnd > nalStart) {\n nals.push(buf.subarray(nalStart, nalEnd));\n }\n i = nalEnd;\n } else {\n i++;\n }\n }\n return nals;\n }\n\n // -----------------------------------------------------------------------\n // ADTS AAC parsing (used for audio metadata exposed via getAudioInfo)\n // -----------------------------------------------------------------------\n\n /** True if `b` starts with an ADTS AAC syncword (0xFFF). */\n private static isAdtsAacFrame(b: Buffer): boolean {\n return b.length >= 2 && b[0] === 0xff && (b[1]! & 0xf0) === 0xf0;\n }\n\n /**\n * Parse an ADTS header into {sampleRate, channels, AudioSpecificConfig hex}.\n * Returns null when the buffer is not a valid ADTS frame.\n */\n private static parseAdtsSamplingInfo(\n b: Buffer,\n ): { sampleRate: number; channels: number; configHex: string } | null {\n if (b.length < 7) return null;\n if (!Go2rtcTcpServer.isAdtsAacFrame(b)) return null;\n\n const samplingIndex = (b[2]! >> 2) & 0x0f;\n const sampleRates = [\n 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000,\n 11025, 8000, 7350,\n ];\n const sampleRate = sampleRates[samplingIndex] ?? null;\n if (!sampleRate) return null;\n\n const channelConfig = ((b[2]! & 0x01) << 2) | ((b[3]! >> 6) & 0x03);\n const channels = channelConfig === 0 ? 1 : channelConfig;\n\n const profile = (b[2]! >> 6) & 0x03;\n const audioObjectType = profile + 1;\n const asc =\n (audioObjectType << 11) | (samplingIndex << 7) | (channelConfig << 3);\n const configHex = Buffer.from([(asc >> 8) & 0xff, asc & 0xff]).toString(\n \"hex\",\n );\n return { sampleRate, channels, configHex };\n }\n\n // -----------------------------------------------------------------------\n // Native stream management\n // -----------------------------------------------------------------------\n\n /**\n * Schedule another startNativeStream() attempt after the given delay.\n * Idempotent: a no-op if a retry is already scheduled, the server is no\n * longer active, or an explicit stop is in progress. Implements unbounded\n * exponential backoff (5s → 60s) so a camera that stays unreachable for\n * minutes (e.g. nightly maintenance reboot) eventually recovers without\n * manual intervention — see issue #16.\n */\n private scheduleNativeStreamRetry(reason: string): void {\n if (!this.active) return;\n if (this.nativeStreamStopping) return;\n if (this.nativeStreamRetryTimer) return;\n\n const delay = this.nativeStreamRetryDelayMs > 0\n ? this.nativeStreamRetryDelayMs\n : 5_000;\n this.logger.info?.(\n `[Go2rtcTcpServer] scheduling native stream retry in ${(delay / 1000).toFixed(0)}s (reason=${reason})`,\n );\n this.nativeStreamRetryTimer = setTimeout(() => {\n this.nativeStreamRetryTimer = undefined;\n if (!this.active) return;\n if (this.nativeStreamStopping) return;\n this.startNativeStream().catch((err) => {\n this.logger.warn?.(\n `[Go2rtcTcpServer] retry of startNativeStream threw: ${err instanceof Error ? err.message : err}`,\n );\n });\n }, delay);\n\n // Exponential backoff: 5 → 10 → 20 → 40 → 60 (capped). Reset on first frame.\n this.nativeStreamRetryDelayMs = Math.min(delay * 2, 60_000);\n }\n\n /**\n * Cancel any pending retry timer and reset the backoff. Called on explicit\n * stop and on first-frame-received so the next failure starts the backoff\n * window from scratch.\n */\n private clearNativeStreamRetry(): void {\n if (this.nativeStreamRetryTimer) {\n clearTimeout(this.nativeStreamRetryTimer);\n this.nativeStreamRetryTimer = undefined;\n }\n this.nativeStreamRetryDelayMs = 0;\n }\n\n private async startNativeStream(): Promise<void> {\n if (this.nativeStreamActive) return;\n\n // Ensure the API's control socket is connected. Battery cameras use\n // idle_disconnect: the control socket closes between stream sessions but\n // the API object stays valid (isClosed = false). ensureConnected()\n // reconnects transparently on-demand.\n if (!this.api.isReady) {\n if (this.api.isClosed) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] API has been explicitly closed — stream cannot start`,\n );\n return;\n }\n try {\n this.logger.info?.(\n `[Go2rtcTcpServer] API not ready (idle disconnect?), calling ensureConnected`,\n );\n await this.api.ensureConnected();\n } catch (e) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] ensureConnected failed: ${e}`,\n );\n // Schedule another attempt with backoff so a transient outage\n // (camera reboot, network blip) eventually recovers without manual\n // intervention.\n this.scheduleNativeStreamRetry(\"ensureConnected failed\");\n return;\n }\n }\n\n this.nativeStreamActive = true;\n\n // Acquire dedicated session if deviceId is set\n let dedicatedClient: import(\"../../client/BaichuanClient\").BaichuanClient | undefined;\n if (this.deviceId) {\n try {\n // Session key MUST start with \"live:\" so the library's resolveSocketTag()\n // assigns a per-profile socket tag (e.g. \"streaming:ch0:main\").\n // Without the \"live:\" prefix, all streams fall back to the shared \"general\"\n // socket, causing streamType mismatches between main/sub/ext.\n const session = await this.api.createDedicatedSession(\n `live:${this.deviceId}:ch${this.channel}:${this.profile}`,\n );\n dedicatedClient = session.client;\n this.dedicatedSessionRelease = session.release;\n } catch (e) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] failed to acquire dedicated session, using shared socket: ${e}`,\n );\n }\n }\n\n this.logger.info?.(\n `[Go2rtcTcpServer] native stream starting channel=${this.channel} profile=${this.profile} dedicated=${!!dedicatedClient}`,\n );\n\n // Per-session flag: true once at least one frame is received.\n // Used in onEnd to distinguish \"camera sleeping (never sent frames)\" from\n // \"camera dropped mid-stream\" — only the latter should trigger a restart.\n let hadFrames = false;\n\n this.nativeFanout = new NativeStreamFanout({\n maxQueueItems: 200,\n createSource: () =>\n createNativeStream(this.api, this.channel, this.profile, {\n variant: this.variant,\n ...(dedicatedClient ? { client: dedicatedClient } : {}),\n }),\n onFrame: (frame) => {\n // Update stream health tracking\n if (!hadFrames) {\n // First frame after (re)start — wipe any pending retry timer and\n // reset the backoff window so the next failure starts fresh.\n this.clearNativeStreamRetry();\n }\n hadFrames = true;\n this.lastFrameAt = Date.now();\n this.totalFramesReceived++;\n\n // Detect video type from first video frame\n if (!frame.audio && (frame.videoType === \"H264\" || frame.videoType === \"H265\")) {\n this.detectedVideoType = frame.videoType;\n }\n\n // Convert and add to prebuffer\n // Video: convert to Annex-B; Audio: store raw ADTS (muxing happens per-client)\n let prebufData: Buffer;\n let isKeyframe: boolean;\n\n if (frame.audio) {\n if (frame.data.length === 0) return;\n // Populate audioInfo on the first valid ADTS frame so getAudioInfo()\n // (used by the stream-diagnostic feature) can return metadata.\n if (!this.audioInfo) {\n const parsed = Go2rtcTcpServer.parseAdtsSamplingInfo(frame.data);\n if (parsed) {\n this.audioInfo = { codec: \"aac-adts\", ...parsed };\n }\n }\n prebufData = frame.data;\n isKeyframe = false;\n } else {\n const annexB = this.convertVideoFrame(frame);\n if (!annexB || annexB.length === 0) return;\n prebufData = annexB;\n isKeyframe = this.isAnnexBKeyframe(annexB, frame.videoType);\n }\n\n const pts = frame.microseconds ?? Date.now() * 1000;\n this.prebuffer.push({\n data: Buffer.from(prebufData),\n time: Date.now(),\n isKeyframe,\n audio: frame.audio,\n pts,\n });\n\n // Trim prebuffer to window\n const cutoff = Date.now() - this.prebufferMaxMs;\n let trimIdx = 0;\n while (trimIdx < this.prebuffer.length && this.prebuffer[trimIdx]!.time < cutoff) {\n trimIdx++;\n }\n if (trimIdx > 0) this.prebuffer.splice(0, trimIdx);\n },\n onError: (error) => {\n this.logger.warn?.(`[Go2rtcTcpServer] native stream error: ${error}`);\n },\n onEnd: () => {\n // Short-circuit only when stopNativeStream() is the trigger — that\n // path performs its own cleanup. The inactivity-timeout force-restart\n // path leaves nativeStreamStopping=false, so we proceed and let the\n // restart branch below decide what to do (issue #16).\n if (this.nativeStreamStopping) return;\n this.nativeStreamActive = false;\n this.nativeFanout = null;\n this.stopStreamHealthMonitor();\n\n const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;\n const diagnosis = silenceMs > this.streamTimeoutMs\n ? \"camera stopped sending frames\"\n : silenceMs >= 0\n ? \"stream source closed\"\n : \"no frames were ever received\";\n this.logger.warn?.(\n `[Go2rtcTcpServer] native stream ended diagnosis=\"${diagnosis}\" ` +\n `lastFrame=${silenceMs >= 0 ? `${(silenceMs / 1000).toFixed(1)}s ago` : \"never\"} ` +\n `totalRx=${this.totalFramesReceived} clients=${this.connectedClients.size}`,\n );\n\n // Release dedicated session\n if (this.dedicatedSessionRelease) {\n this.dedicatedSessionRelease().catch(() => {});\n this.dedicatedSessionRelease = undefined;\n }\n\n // Auto-restart policy:\n // - AC-powered cameras (prestartStream=true): always restart to keep\n // the stream warm. They are expected to come back online quickly.\n // - Battery cameras (prestartStream=false): NEVER auto-restart. The\n // native stream ending for a battery camera means the camera went\n // back to sleep. Restarting would immediately wake the camera\n // again, creating an awake→sleep→awake loop for as long as a\n // downstream consumer (go2rtc RTSP client, HA snapshot poller,\n // etc.) holds the TCP connection open. Instead we drop all TCP\n // clients so go2rtc sees EOF and propagates the disconnect to its\n // consumers — they must explicitly reconnect to re-wake the camera.\n if (!this.prestartStream) {\n this.logger.info?.(\n `[Go2rtcTcpServer] battery native stream ended hadFrames=${hadFrames} ` +\n `channel=${this.channel} profile=${this.profile} — dropping ${this.connectedClients.size} client(s) to prevent wake loop`,\n );\n for (const [, sock] of this.clientSockets) {\n sock.destroy();\n }\n } else if (this.active) {\n // If the device explicitly rejected this profile (response_code 400)\n // there is no point in restarting — the camera will reject every\n // subsequent attempt, spamming the logs. Drop clients and stay\n // stopped until the consumer explicitly removes/recreates this\n // server (or the API instance is recycled).\n if (\n typeof this.api.isStreamProfileRejected === \"function\" &&\n this.api.isStreamProfileRejected(this.channel, this.profile)\n ) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] profile rejected by device channel=${this.channel} profile=${this.profile} — not restarting`,\n );\n for (const [, sock] of this.clientSockets) {\n sock.destroy();\n }\n return;\n }\n this.logger.info?.(\n `[Go2rtcTcpServer] restarting native stream (clients=${this.connectedClients.size}, prestart=${this.prestartStream})`,\n );\n this.startNativeStream();\n }\n },\n });\n\n this.nativeFanout.start();\n this.startStreamHealthMonitor();\n }\n\n private async stopNativeStream(): Promise<void> {\n // Mark the teardown as explicit so the fanout's onEnd skips its\n // auto-restart branch (otherwise an explicit stop would loop right back\n // to a fresh native stream).\n this.nativeStreamStopping = true;\n this.nativeStreamActive = false;\n // Cancel any pending unbounded-retry timer so we don't resurrect the\n // stream we're explicitly tearing down.\n this.clearNativeStreamRetry();\n this.stopStreamHealthMonitor();\n const fanout = this.nativeFanout;\n this.nativeFanout = null;\n try {\n if (fanout) {\n await fanout.stop();\n }\n this.prebuffer = [];\n\n if (this.dedicatedSessionRelease) {\n await this.dedicatedSessionRelease().catch(() => {});\n this.dedicatedSessionRelease = undefined;\n }\n } finally {\n this.nativeStreamStopping = false;\n }\n }\n\n // -----------------------------------------------------------------------\n // Stream health monitoring\n // -----------------------------------------------------------------------\n\n private startStreamHealthMonitor(): void {\n this.stopStreamHealthMonitor();\n if (this.streamTimeoutMs <= 0) return;\n\n this.lastFrameAt = Date.now();\n this.streamHealthTimer = setInterval(() => {\n if (!this.nativeStreamActive || !this.active) {\n this.stopStreamHealthMonitor();\n return;\n }\n\n const silenceMs = Date.now() - this.lastFrameAt;\n if (silenceMs > this.streamTimeoutMs) {\n this.logger.warn?.(\n `[Go2rtcTcpServer] stream inactivity timeout: no frames for ${(silenceMs / 1000).toFixed(1)}s (threshold=${this.streamTimeoutMs}ms), ` +\n `totalReceived=${this.totalFramesReceived} clients=${this.connectedClients.size} — forcing stream restart`,\n );\n this.stopStreamHealthMonitor();\n\n // Force-stop the native stream; the onEnd callback will auto-restart\n // if clients are connected or prestartStream is enabled. We do NOT\n // set nativeStreamStopping here — that flag is reserved for explicit\n // stopNativeStream() calls and tells onEnd to skip its restart branch.\n // (Pre-fix: this block flipped nativeStreamActive=false, which the\n // old onEnd guard interpreted as \"explicit stop\" and skipped the\n // restart, leaving the stream dead until manual intervention — see\n // issue #16.)\n const fanout = this.nativeFanout;\n if (fanout) {\n this.nativeStreamActive = false;\n this.nativeFanout = null;\n fanout.stop().catch(() => {});\n }\n }\n }, Math.min(this.streamTimeoutMs / 2, 5_000));\n }\n\n private stopStreamHealthMonitor(): void {\n if (this.streamHealthTimer) {\n clearInterval(this.streamHealthTimer);\n this.streamHealthTimer = undefined;\n }\n }\n\n // -----------------------------------------------------------------------\n // Client lifecycle\n // -----------------------------------------------------------------------\n\n private removeClient(clientId: string, reason?: string): void {\n if (!this.connectedClients.has(clientId)) return;\n this.connectedClients.delete(clientId);\n this.clientSockets.delete(clientId);\n\n const silenceMs = this.lastFrameAt > 0 ? Date.now() - this.lastFrameAt : -1;\n const silenceInfo = silenceMs >= 0 ? ` lastFrame=${(silenceMs / 1000).toFixed(1)}s ago` : \"\";\n this.logger.info?.(\n `[Go2rtcTcpServer] client disconnected id=${clientId} reason=${reason ?? \"unknown\"}` +\n ` remaining=${this.connectedClients.size} totalRx=${this.totalFramesReceived} totalTx=${this.totalVideoFramesWritten}${silenceInfo}`,\n );\n this.emit(\"clientDisconnected\", clientId);\n\n if (this.connectedClients.size === 0 && !this.prestartStream) {\n // Only schedule stop if prestart is off. When prestart is on, the native\n // stream stays alive permanently so go2rtc can reconnect at any time.\n this.scheduleStop();\n }\n }\n\n private scheduleStop(): void {\n if (this.stopGraceTimer) return;\n this.logger.info?.(\n `[Go2rtcTcpServer] no clients, scheduling stream stop in ${this.gracePeriodMs}ms`,\n );\n this.stopGraceTimer = setTimeout(async () => {\n this.stopGraceTimer = undefined;\n if (this.connectedClients.size === 0 && this.nativeStreamActive) {\n this.logger.info?.(\"[Go2rtcTcpServer] grace period expired, stopping native stream\");\n await this.stopNativeStream();\n }\n }, this.gracePeriodMs);\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 // `+genpts` generates uniform PTS from `-r` for raw Annex-B input. We\n // deliberately do NOT pass `-use_wallclock_as_timestamps 1`: that\n // overrides the generated PTS with the host wallclock at frame ARRIVAL\n // time, and the network stream is bursty so the resulting PTS is\n // uneven. With `-r` forcing a target rate downstream, ffmpeg then\n // drops/duplicates frames to match — visible as the periodic stutter\n // reported on local-restreamer recordings (issue #11).\n \"-fflags\", \"+genpts\",\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 // createNativeStream automatically acquires a dedicated socket from the pool.\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\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 { AacToOpusTranscoder } from \"./AacToOpusTranscoder\";\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 /** Limit the UDP port range used by ICE (useful for Docker port publishing) */\n icePortRange?: [number, number];\n /** Extra host addresses to advertise as candidates (e.g. host LAN IP) */\n iceAdditionalHostAddresses?: string[];\n /** Force relay-only (TURN) if needed */\n iceTransportPolicy?: \"all\" | \"relay\";\n /**\n * Path to ffmpeg used for AAC → Opus audio transcoding. Defaults to\n * \"ffmpeg\" on PATH. Pass an empty string to disable audio (the audio track\n * will still appear in the SDP for SDP-stability, but stay silent).\n */\n ffmpegPath?: string;\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 // AAC → Opus transcoder for the audio track. Lazy-spun on the first audio\n // frame so non-audio sessions don't fork an ffmpeg process.\n audioTranscoder?: AacToOpusTranscoder | null;\n // RTP sequence + timestamp counters for the audio track. ffmpeg's RTP\n // output uses its own SSRC, so we extract just the opus payload and rewrap\n // it with our audio track's SSRC and our own monotonic seq.\n audioRtpSequence?: number;\n audioRtpTimestampBase?: number;\n audioStartedLogged?: boolean;\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 icePortRange: this.options.icePortRange,\n iceAdditionalHostAddresses: this.options.iceAdditionalHostAddresses,\n iceTransportPolicy: this.options.iceTransportPolicy,\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 // Stop audio transcoder (kills the per-session ffmpeg subprocess and\n // closes the UDP loopback socket).\n if (session.audioTranscoder) {\n try {\n await session.audioTranscoder.stop();\n } catch (err) {\n this.log(\"debug\", `Error stopping audio transcoder: ${err}`);\n }\n session.audioTranscoder = 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 // createNativeStream automatically acquires a dedicated socket from the pool.\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 from the camera (AAC ADTS or ADPCM). Hand off to the\n // ffmpeg-backed transcoder, which decodes + re-encodes to Opus and\n // returns RTP-ready packets via the `packet` event.\n session.stats.audioFrames++;\n if (session.stats.audioFrames === 1) {\n const head =\n frame.data && frame.data.length > 0\n ? frame.data.subarray(0, Math.min(8, frame.data.length)).toString(\"hex\")\n : \"(empty)\";\n this.log(\n \"info\",\n `First audio frame for ${session.id}: codec=${frame.codec ?? \"?\"} bytes=${frame.data?.length ?? 0} head=${head}`,\n );\n }\n if (\n this.options.ffmpegPath !== \"\" &&\n frame.data &&\n frame.data.length > 0\n ) {\n // ADPCM is uncommon on the cameras we've tested — start with AAC\n // only; ADPCM support can be added by switching the input format.\n await this.ensureAudioTranscoder(session, werift);\n session.audioTranscoder?.feedAac(frame.data);\n }\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 (now includes audio frame counter\n // so we can see whether the camera ever yields audio at all).\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} video frames, ${packetsSentSinceLastLog} packets, ${Math.round(session.stats.bytesSent / 1024)} KB | audio frames=${session.stats.audioFrames}`,\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 * Lazily start the AAC → Opus transcoder for `session` and wire it to the\n * audio RTP track. ffmpeg writes RTP-formatted Opus packets back to a UDP\n * loopback the transcoder owns; we strip the RTP header and rewrap the\n * Opus payload with our audioTrack's SSRC so the browser receives a\n * coherent stream.\n */\n private async ensureAudioTranscoder(\n session: WebRTCSession,\n werift: any,\n ): Promise<void> {\n if (session.audioTranscoder !== undefined) return;\n\n const { RtpPacket, RtpHeader } = werift;\n const ssrc = session.audioTrack?.ssrc ?? Math.floor(Math.random() * 0xffffffff);\n\n const transcoder = new AacToOpusTranscoder({\n ...(this.options.ffmpegPath ? { ffmpegPath: this.options.ffmpegPath } : {}),\n logger: (level, msg) => this.log(level, msg),\n });\n\n session.audioRtpSequence = Math.floor(Math.random() * 0xffff);\n session.audioRtpTimestampBase = 0;\n\n transcoder.on(\"packet\", ({ payload, timestamp, marker }) => {\n try {\n // Anchor the RTP timestamp to the first packet so the value space we\n // send to the browser starts near 0 (avoids negative jitter buffer\n // effects) and stays consistent across reconnects.\n if (\n session.audioRtpTimestampBase === undefined ||\n session.audioRtpTimestampBase === 0\n ) {\n session.audioRtpTimestampBase = timestamp;\n }\n const localTs = (timestamp - session.audioRtpTimestampBase) >>> 0;\n const seq = session.audioRtpSequence!;\n session.audioRtpSequence = (seq + 1) & 0xffff;\n\n const header = new RtpHeader({\n version: 2,\n padding: false,\n extension: false,\n marker,\n // Werift assigns 111 to Opus by default in the receiver SDP, but it\n // also accepts other PTs. Use 111 for compatibility with the offer\n // we generated earlier.\n payloadType: 111,\n sequenceNumber: seq,\n timestamp: localTs,\n ssrc,\n });\n const rtp = new RtpPacket(header, payload);\n session.audioTrack?.writeRtp(rtp);\n if (!session.audioStartedLogged) {\n session.audioStartedLogged = true;\n this.log(\n \"info\",\n `Audio RTP started for ${session.id} (PT=111, ssrc=${ssrc})`,\n );\n }\n } catch (err) {\n this.log(\n \"warn\",\n `audio writeRtp failed for ${session.id}: ${(err as Error).message}`,\n );\n }\n });\n\n transcoder.on(\"error\", (err) => {\n this.log(\"error\", `audio transcoder error for ${session.id}: ${err.message}`);\n });\n transcoder.on(\"exit\", (code) => {\n this.log(\"info\", `audio transcoder exited (${code}) for ${session.id}`);\n });\n\n try {\n await transcoder.start();\n session.audioTranscoder = transcoder;\n } catch (err) {\n this.log(\n \"error\",\n `failed to start audio transcoder for ${session.id}: ${(err as Error).message}`,\n );\n session.audioTranscoder = null;\n }\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 as a sequence of chunks. Every binary message\n // carries a uniform 4-byte chunk header so the client can reassemble\n // without ambiguity:\n // [0..1] chunk index (u16 BE)\n // [2..3] total chunks (u16 BE)\n //\n // Single-chunk frames are sent as `[0, 1]` so the wire format is consistent\n // — important for H.265 where a 4K IDR frame can run 400KB and is always\n // chunked, while inter-frames may fit in one message. Without a uniform\n // header the client would have no way to tell which is which.\n const CHUNK_HEADER_LEN = 4;\n const MAX_PAYLOAD_PER_CHUNK = 16000 - CHUNK_HEADER_LEN;\n\n try {\n const totalChunks = Math.max(\n 1,\n Math.ceil(packet.length / MAX_PAYLOAD_PER_CHUNK),\n );\n for (let i = 0; i < totalChunks; i++) {\n const start = i * MAX_PAYLOAD_PER_CHUNK;\n const end = Math.min(start + MAX_PAYLOAD_PER_CHUNK, packet.length);\n const chunk = packet.subarray(start, end);\n const chunkHeader = Buffer.alloc(CHUNK_HEADER_LEN);\n chunkHeader.writeUInt16BE(i, 0);\n chunkHeader.writeUInt16BE(totalChunks, 2);\n session.videoDataChannel.send(Buffer.concat([chunkHeader, chunk]));\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 * AAC ADTS → Opus transcoder for live WebRTC.\n *\n * Reolink cameras send audio as AAC LC (ADTS frames) over the Baichuan video\n * stream. WebRTC audio in browsers is Opus-only, so we shell out to ffmpeg to\n * decode AAC and re-encode to Opus. The transcoded Opus packets come back to\n * us as raw RTP via a UDP loopback — far simpler than parsing OGG pages or\n * relying on native bindings.\n *\n * Pipeline:\n *\n * AAC frames ──stdin──▶ ffmpeg ──RTP/UDP──▶ this.socket\n * │\n * └─ -c:a libopus -ar 48000 -ac 2\n * -frame_duration 20 -f rtp\n *\n * For each RTP packet received, we strip the 12-byte RTP header, take the\n * Opus payload + the 32-bit timestamp ffmpeg generated, and forward it via\n * the consumer-supplied `onPacket` callback. The consumer (e.g.\n * BaichuanWebRTCServer) re-wraps the payload into an RTP packet with the\n * audio track's own SSRC and sequence number and calls `audioTrack.writeRtp()`.\n *\n * This keeps the transcoder decoupled from werift specifics and makes it\n * easy to unit test (the consumer can pass any onPacket handler).\n */\n\nimport { ChildProcess, spawn } from \"node:child_process\";\nimport { createSocket, Socket } from \"node:dgram\";\nimport { EventEmitter } from \"node:events\";\n\nexport interface AacToOpusTranscoderOptions {\n /** Path to the ffmpeg binary. Defaults to \"ffmpeg\" on PATH. */\n ffmpegPath?: string;\n /** Sample rate of the output Opus stream. Browsers require 48000. */\n opusSampleRate?: number;\n /** Channel count of the output Opus stream. */\n opusChannels?: 1 | 2;\n /** Opus frame duration in ms. 20 is the WebRTC default. */\n opusFrameMs?: 10 | 20 | 40 | 60;\n /** Opus target bitrate in bits/sec. 64k is decent for camera audio. */\n opusBitrate?: number;\n /** Optional logger for diagnostic messages. */\n logger?: (level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string) => void;\n}\n\nexport interface OpusPacket {\n /** Raw Opus payload (no RTP header). */\n payload: Buffer;\n /** RTP timestamp ffmpeg assigned to the packet (48kHz clock). */\n timestamp: number;\n /** Marker bit from the originating RTP packet (rare for audio). */\n marker: boolean;\n}\n\ntype Events = {\n packet: [OpusPacket];\n error: [Error];\n exit: [code: number | null];\n};\n\nexport class AacToOpusTranscoder extends EventEmitter<Events> {\n private readonly opts: Required<Omit<AacToOpusTranscoderOptions, \"logger\">> &\n Pick<AacToOpusTranscoderOptions, \"logger\">;\n private socket: Socket | null = null;\n private ffmpeg: ChildProcess | null = null;\n private port = 0;\n private starting: Promise<void> | null = null;\n private stopped = false;\n\n constructor(options: AacToOpusTranscoderOptions = {}) {\n super();\n this.opts = {\n ffmpegPath: options.ffmpegPath ?? \"ffmpeg\",\n opusSampleRate: options.opusSampleRate ?? 48000,\n opusChannels: options.opusChannels ?? 2,\n opusFrameMs: options.opusFrameMs ?? 20,\n opusBitrate: options.opusBitrate ?? 64_000,\n ...(options.logger !== undefined ? { logger: options.logger } : {}),\n };\n }\n\n private log(level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string): void {\n this.opts.logger?.(level, `[AacToOpusTranscoder] ${message}`);\n }\n\n /**\n * Allocate the UDP loopback socket and spawn ffmpeg. Must be awaited before\n * the first call to `feedAac`.\n */\n async start(): Promise<void> {\n if (this.starting) return this.starting;\n this.starting = this._start();\n return this.starting;\n }\n\n private async _start(): Promise<void> {\n if (this.stopped) throw new Error(\"transcoder stopped\");\n\n // 1) Bind a UDP socket on localhost. Port 0 lets the OS pick a free port.\n this.socket = createSocket(\"udp4\");\n await new Promise<void>((resolve, reject) => {\n this.socket!.once(\"error\", reject);\n this.socket!.bind({ address: \"127.0.0.1\", port: 0 }, () => {\n this.socket!.removeListener(\"error\", reject);\n this.port = (this.socket!.address() as { port: number }).port;\n resolve();\n });\n });\n this.log(\"info\", `UDP loopback bound on 127.0.0.1:${this.port}`);\n\n this.socket.on(\"message\", (rtpPacket) => this.handleRtp(rtpPacket));\n this.socket.on(\"error\", (err) => {\n this.log(\"error\", `socket error: ${err.message}`);\n this.emit(\"error\", err);\n });\n\n // 2) Spawn ffmpeg. We use `-loglevel warning` to silence the per-frame\n // info chatter ffmpeg emits at info level.\n const args = [\n \"-loglevel\",\n \"warning\",\n \"-fflags\",\n \"nobuffer\",\n \"-f\",\n \"aac\",\n \"-i\",\n \"pipe:0\",\n \"-c:a\",\n \"libopus\",\n \"-ar\",\n String(this.opts.opusSampleRate),\n \"-ac\",\n String(this.opts.opusChannels),\n \"-application\",\n \"audio\",\n \"-frame_duration\",\n String(this.opts.opusFrameMs),\n \"-b:a\",\n String(this.opts.opusBitrate),\n // Important: disable VBR so the output rate matches the configured\n // bitrate consistently — easier on the browser jitter buffer.\n \"-vbr\",\n \"off\",\n \"-f\",\n \"rtp\",\n `rtp://127.0.0.1:${this.port}`,\n ];\n this.log(\"info\", `spawning ffmpeg with: ${this.opts.ffmpegPath} ${args.join(\" \")}`);\n\n this.ffmpeg = spawn(this.opts.ffmpegPath, args, {\n stdio: [\"pipe\", \"ignore\", \"pipe\"],\n });\n\n this.ffmpeg.on(\"error\", (err) => {\n this.log(\"error\", `ffmpeg spawn error: ${err.message}`);\n this.emit(\"error\", err);\n });\n this.ffmpeg.stderr?.on(\"data\", (chunk: Buffer) => {\n // ffmpeg sends important warnings to stderr; surface at debug.\n this.log(\"debug\", `ffmpeg stderr: ${chunk.toString().trim()}`);\n });\n this.ffmpeg.on(\"exit\", (code) => {\n this.log(code === 0 ? \"info\" : \"warn\", `ffmpeg exited code=${code}`);\n this.emit(\"exit\", code);\n });\n }\n\n /**\n * Feed one or more concatenated ADTS AAC frames to ffmpeg. Returns the\n * number of bytes written. Safe to call before a frame is fully buffered.\n */\n feedAac(buf: Buffer): boolean {\n if (this.stopped) return false;\n if (!this.ffmpeg?.stdin || !this.ffmpeg.stdin.writable) {\n this.log(\"debug\", \"drop AAC frame — ffmpeg stdin not writable\");\n return false;\n }\n return this.ffmpeg.stdin.write(buf);\n }\n\n /**\n * Close stdin (ffmpeg exits cleanly on EOF) and tear down the UDP socket.\n */\n async stop(): Promise<void> {\n if (this.stopped) return;\n this.stopped = true;\n try {\n this.ffmpeg?.stdin?.end();\n } catch {\n // ignore\n }\n if (this.ffmpeg && this.ffmpeg.exitCode === null) {\n // Give ffmpeg a moment to exit on stdin close before we kill it.\n await new Promise<void>((resolve) => {\n const t = setTimeout(() => {\n this.ffmpeg?.kill(\"SIGKILL\");\n resolve();\n }, 500);\n this.ffmpeg?.once(\"exit\", () => {\n clearTimeout(t);\n resolve();\n });\n });\n }\n this.ffmpeg = null;\n if (this.socket) {\n try { this.socket.close(); } catch { /* ignore */ }\n this.socket = null;\n }\n this.log(\"info\", \"stopped\");\n }\n\n /**\n * Parse an RTP packet ffmpeg emitted on the loopback socket and surface\n * its Opus payload. RTP header layout (RFC 3550):\n *\n * 0 1 2 3\n * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n * |V=2|P|X| CC |M| PT | sequence number |\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n * | timestamp |\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n * | synchronization source (SSRC) identifier |\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *\n * We honor the CC, X (extension) and P (padding) bits to compute the\n * payload offset / length correctly even if ffmpeg ever emits those.\n */\n private handleRtp(packet: Buffer): void {\n if (packet.length < 12) return;\n const b0 = packet[0]!;\n const b1 = packet[1]!;\n const version = b0 >> 6;\n if (version !== 2) return;\n const padding = (b0 & 0x20) !== 0;\n const extension = (b0 & 0x10) !== 0;\n const csrcCount = b0 & 0x0f;\n const marker = (b1 & 0x80) !== 0;\n let offset = 12 + csrcCount * 4;\n if (extension) {\n if (packet.length < offset + 4) return;\n const extLen = packet.readUInt16BE(offset + 2);\n offset += 4 + extLen * 4;\n }\n let end = packet.length;\n if (padding) {\n const pad = packet[packet.length - 1]!;\n end -= pad;\n }\n if (offset >= end) return;\n\n const timestamp = packet.readUInt32BE(4);\n const payload = Buffer.from(packet.subarray(offset, end));\n this.emit(\"packet\", { payload, timestamp, marker });\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 type { BaichuanVideoStream } from \"./BaichuanVideoStream\";\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 /**\n * External video stream from a shared pool (e.g. createRfc4571TcpServer).\n * When provided, the HLS server subscribes to this stream's events instead\n * of creating its own via createNativeStream. This allows multiple outputs\n * (MJPEG, HLS, WebRTC) to share a single camera streaming session.\n */\n externalVideoStream?: BaichuanVideoStream;\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 externalVideoStream: BaichuanVideoStream | undefined;\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.externalVideoStream = options.externalVideoStream;\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 // Use external video stream if provided (shared pool), otherwise create our own.\n if (this.externalVideoStream) {\n this.nativeStream = this.wrapVideoStreamAsGenerator(this.externalVideoStream);\n } else {\n // createNativeStream automatically acquires a dedicated socket from the pool.\n this.nativeStream = createNativeStream(\n this.api,\n this.channel,\n this.profile,\n this.variant ? { variant: this.variant } : undefined,\n );\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 /**\n * Wrap a BaichuanVideoStream's videoAccessUnit events into an async generator\n * compatible with createNativeStream's output format.\n */\n private async *wrapVideoStreamAsGenerator(\n videoStream: BaichuanVideoStream,\n ): AsyncGenerator<{\n audio: boolean;\n data: Buffer;\n videoType?: \"H264\" | \"H265\";\n isKeyframe?: boolean;\n microseconds: number | null;\n }, void, unknown> {\n type Frame = {\n data: Buffer;\n isKeyframe: boolean;\n videoType: \"H264\" | \"H265\";\n microseconds: number;\n };\n\n const queue: Frame[] = [];\n let resolve: (() => void) | null = null;\n let done = false;\n\n const onFrame = (au: Frame) => {\n queue.push(au);\n resolve?.();\n resolve = null;\n };\n\n const onClose = () => {\n done = true;\n resolve?.();\n resolve = null;\n };\n\n const onError = () => {\n done = true;\n resolve?.();\n resolve = null;\n };\n\n videoStream.on(\"videoAccessUnit\", onFrame);\n videoStream.on(\"close\", onClose);\n videoStream.on(\"error\", onError);\n\n try {\n while (!done) {\n if (queue.length === 0) {\n await new Promise<void>((r) => { resolve = r; });\n }\n while (queue.length > 0) {\n const frame = queue.shift()!;\n yield {\n audio: false,\n data: frame.data,\n videoType: frame.videoType,\n isKeyframe: frame.isKeyframe,\n microseconds: frame.microseconds,\n };\n }\n }\n } finally {\n videoStream.removeListener(\"videoAccessUnit\", onFrame);\n videoStream.removeListener(\"close\", onClose);\n videoStream.removeListener(\"error\", onError);\n }\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 // `+genpts` makes ffmpeg generate uniform PTS from the declared `-r`\n // when the raw H.264/H.265 input has none. We deliberately do NOT use\n // `-use_wallclock_as_timestamps 1` here: it replaces the generated\n // PTS with the host wallclock at FRAME ARRIVAL time, and because the\n // camera ships frames in bursty network reads, the resulting PTS\n // sequence is uneven. With `-r 25` (or anything else) forcing a\n // target rate downstream, ffmpeg then drops/duplicates frames to\n // match — visible as the periodic stutter / pulsing reported on\n // local-restreamer recordings (issue #11).\n \"-fflags\",\n \"+genpts\",\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","/**\n * Motion-detection zone grid helpers.\n *\n * Reolink's GetMdAlarm response (cmd_id=46) carries the active detection\n * region as a base64-encoded bitmap inside `<scope><valueTable>...</valueTable></scope>`.\n * The bitmap has one bit per grid cell:\n *\n * <scope>\n * <columns>96</columns>\n * <rows>64</rows>\n * <valueTable>{base64 of columns*rows bits, packed MSB-first per byte}</valueTable>\n * </scope>\n *\n * The same shape is reused by AI detection (`<AiDetectCfg><area>...</area>`)\n * — only the column/row counts differ across firmwares. Use the helpers\n * here to round-trip between the camera's base64 string and a flat boolean\n * grid the UI can render and edit.\n */\n\nexport interface MotionZoneScope {\n /** Active region width (effective grid columns, `<width>` in MD). */\n width: number;\n /** Active region height (effective grid rows, `<height>` in MD). */\n height: number;\n /** Bitmap columns reported by `<scope><columns>`. */\n columns: number;\n /** Bitmap rows reported by `<scope><rows>`. */\n rows: number;\n /** Flat `width × height` array, row-major. `true` = cell included. */\n cells: boolean[];\n}\n\n/**\n * Decode the base64 `valueTable` from a `<scope>` (or `<area>`) into a flat\n * boolean grid. Bytes are packed MSB-first: bit 7 of byte 0 is cell (0,0).\n *\n * The camera ships TWO pairs of dimensions: `<scope><columns>×<rows>`\n * (bitmap size — typically 96×64) and `<width>×<height>` in the parent\n * block (the effective motion grid — typically smaller, e.g. 60×33 on\n * E1 Zoom). The user-editable region matches `width × height`, not the\n * full bitmap; bits past that are camera-side padding that stays 0.\n *\n * When `width`/`height` are omitted we treat the whole bitmap as the\n * active region (back-compat).\n *\n * Throws if the base64 contains too few bytes for `columns*rows` bits.\n */\nexport function decodeMotionScopeBitmap(\n valueTable: string,\n columns: number,\n rows: number,\n width: number = columns,\n height: number = rows,\n): MotionZoneScope {\n const trimmed = valueTable.trim().replace(/[^A-Za-z0-9+/=]/g, \"\");\n const bytes = base64DecodeToBytes(trimmed);\n const totalBits = columns * rows;\n if (bytes.length * 8 < totalBits) {\n throw new Error(\n `valueTable too short: have ${bytes.length * 8} bits, need ${totalBits}`,\n );\n }\n const w = Math.min(width, columns);\n const h = Math.min(height, rows);\n const cells = new Array<boolean>(w * h);\n for (let r = 0; r < h; r++) {\n for (let c = 0; c < w; c++) {\n const bitIndex = r * columns + c;\n const byteIdx = bitIndex >> 3;\n const bitIdx = 7 - (bitIndex & 7);\n cells[r * w + c] = ((bytes[byteIdx] ?? 0) >> bitIdx) & 1 ? true : false;\n }\n }\n return { width: w, height: h, columns, rows, cells };\n}\n\n/**\n * Encode a `width × height` boolean grid back into the camera's\n * `columns × rows` `valueTable`. Bits outside the active region stay 0,\n * matching what the camera ships on the way down.\n *\n * `scope.cells.length` must equal `scope.width * scope.height`.\n */\nexport function encodeMotionScopeBitmap(scope: MotionZoneScope): string {\n const bytes = new Uint8Array(Math.ceil((scope.columns * scope.rows) / 8));\n for (let r = 0; r < scope.height; r++) {\n for (let c = 0; c < scope.width; c++) {\n const on = scope.cells[r * scope.width + c];\n if (!on) continue;\n const bitIndex = r * scope.columns + c;\n const byteIdx = bitIndex >> 3;\n const bitIdx = 7 - (bitIndex & 7);\n bytes[byteIdx] = (bytes[byteIdx] ?? 0) | (1 << bitIdx);\n }\n }\n return base64EncodeBytes(bytes);\n}\n\n/**\n * Convenience: build an \"everything enabled\" grid of the given dimensions.\n * Useful when the camera response has no `<valueTable>` and we want to\n * start the user off with a clean slate.\n */\nexport function fullCoverageScope(\n columns: number,\n rows: number,\n width: number = columns,\n height: number = rows,\n): MotionZoneScope {\n return {\n width,\n height,\n columns,\n rows,\n cells: new Array<boolean>(width * height).fill(true),\n };\n}\n\n// ───────────────────────── Base64 helpers ─────────────────────────\n// We don't use `Buffer` here because this module is also imported by\n// the browser-side React tab. `Uint8Array` works in both environments.\n\nconst B64_ALPHABET =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction base64EncodeBytes(bytes: Uint8Array): string {\n let out = \"\";\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i] ?? 0;\n const b1 = bytes[i + 1] ?? 0;\n const b2 = bytes[i + 2] ?? 0;\n out += B64_ALPHABET[b0 >> 2]!;\n out += B64_ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)]!;\n out += i + 1 < bytes.length ? B64_ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)]! : \"=\";\n out += i + 2 < bytes.length ? B64_ALPHABET[b2 & 0x3f]! : \"=\";\n }\n return out;\n}\n\nfunction base64DecodeToBytes(b64: string): Uint8Array {\n const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, \"=\");\n const stripped = padded.replace(/=+$/, \"\");\n const out = new Uint8Array(Math.floor((stripped.length * 6) / 8));\n let bits = 0;\n let value = 0;\n let outIdx = 0;\n for (const ch of stripped) {\n const idx = B64_ALPHABET.indexOf(ch);\n if (idx < 0) continue;\n value = (value << 6) | idx;\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[outIdx++] = (value >> bits) & 0xff;\n }\n }\n return out.subarray(0, outIdx);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAM,cAAc,OAClB,GACA,IACA,UACe;AACf,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,QAAQ,KAAK;AAAA,MACxB;AAAA,MACA,IAAI,QAAW,CAAC,GAAG,WAAW;AAC5B,YAAI;AAAA,UACF,MAAM,OAAO,IAAI,MAAM,GAAG,KAAK,oBAAoB,EAAE,IAAI,CAAC;AAAA,UAC1D;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH,UAAE;AACA,QAAI,EAAG,cAAa,CAAC;AAAA,EACvB;AACF;AA0EO,IAAM,oBAAN,MAAwB;AAAA,EAO7B,YACmB,KACjB,SACA;AAFiB;AAGjB,SAAK,SAAS,SAAS;AACvB,SAAK,eAAe,SAAS,gBAAgB,IAAI,KAAK;AAGtD,UAAM,oBAAoB,SAAS,qBAAqB;AACxD,SAAK,eAAe,YAAY,MAAM;AACpC,WAAK,KAAK,uBAAuB;AAAA,IACnC,GAAG,iBAAiB;AAAA,EACtB;AAAA,EAlBQ,WAAW,oBAAI,IAA6B;AAAA,EACnC;AAAA,EACA;AAAA,EACT;AAAA,EACA,gBAAgB,oBAAI,IAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBvD,MAAM,cAAc,QAgBS;AAC3B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AAEJ,QAAI;AAEF,UAAI,QAAQ,KAAK,SAAS,IAAI,UAAU;AAExC,YAAM,aAAa,YAAY,mBAAmB,YAAY;AAC9D,YAAM,YAAY,QAAQ,SAAS,KAAK;AAMxC,UAAI,CAAC,SAAS,WAAW;AACvB,aAAK,QAAQ;AAAA,UACX,yFAAyF,UAAU,IAAI,OAAO;AAAA,QAChH;AACA,eAAO;AAAA,UACL,YAAY;AAAA,UACZ,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,iBAAiB;AAAA,YACjB,QAAQ;AAAA,YACR,eAAe;AAAA,UACjB;AAAA,UACA,MAAM;AAAA,QACR;AAAA,MACF;AAEA,UAAI,CAAC,OAAO;AAEV,YAAI,CAAC,YAAY;AACf,iBAAO;AAAA,YACL,YAAY;AAAA,YACZ,SAAS,EAAE,gBAAgB,aAAa;AAAA,YACxC,MAAM;AAAA,UACR;AAAA,QACF;AAKA,cAAM,UAAU,sBAAsB;AACtC,cAAM,KAAK,iBAAiB,SAAS,YAAY;AAE/C,kBAAQ,KAAK,SAAS,IAAI,UAAU;AACpC,cAAI,MAAO;AAEX,cAAI,oBAAoB;AACtB,kBAAM,KAAK;AAAA,cACT;AAAA,cACA;AAAA,YACF;AAAA,UACF;AAEA,eAAK,QAAQ;AAAA,YACX,6CAA6C,UAAU;AAAA,UACzD;AAEA,eAAK,QAAQ;AAAA,YACX,wCAAwC,UAAU;AAAA,UACpD;AACA,gBAAM,gBAAgB,MAAM,cAAc;AAE1C,eAAK,QAAQ;AAAA,YACX,iEAAiE,UAAU;AAAA,UAC7E;AACA,gBAAM,UAAU,MAAM;AAAA,YACpB,KAAK,IAAI,gCAAgC;AAAA,cACvC,SAAS,cAAc;AAAA,cACvB,UAAU,cAAc;AAAA,cACxB,GAAI,cAAc,UAAU,UAAa;AAAA,gBACvC,OAAO,cAAc;AAAA,cACvB;AAAA,cACA,GAAI,KAAK,UAAU,EAAE,QAAQ,KAAK,OAAO;AAAA,cACzC,GAAI,cAAc,YAAY;AAAA,gBAC5B,UAAU,cAAc;AAAA,cAC1B;AAAA,cACA,qBAAqB,cAAc,uBAAuB;AAAA,cAC1D,oBAAoB,cAAc,sBAAsB;AAAA,YAC1D,CAAC;AAAA,YACD;AAAA,YACA;AAAA,UACF;AAIA,cAAI;AACF,kBAAM;AAAA,cACJ,QAAQ,aAAa;AAAA,cACrB;AAAA,cACA;AAAA,YACF;AAAA,UACF,SAAS,GAAG;AACV,iBAAK,QAAQ;AAAA,cACX,iEAAiE,UAAU,KAAK,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,YAC5H;AAAA,UACF;AAEA,kBAAQ;AAAA,YACN;AAAA,YACA,WAAW,KAAK,IAAI;AAAA,YACpB,cAAc,KAAK,IAAI;AAAA,UACzB;AACA,eAAK,SAAS,IAAI,YAAY,KAAK;AAEnC,eAAK,QAAQ;AAAA,YACX,sCAAsC,UAAU;AAAA,UAClD;AAAA,QACF,CAAC;AAGD,gBAAQ,KAAK,SAAS,IAAI,UAAU;AACpC,YAAI,CAAC,OAAO;AACV,iBAAO;AAAA,YACL,YAAY;AAAA,YACZ,SAAS;AAAA,cACP,gBAAgB;AAAA,cAChB,iBAAiB;AAAA,cACjB,QAAQ;AAAA,YACV;AAAA,YACA,MAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAGA,YAAM,eAAe,KAAK,IAAI;AAG9B,UAAI,YAAY;AACd,eAAO,KAAK,cAAc,MAAM,SAAS,YAAY,UAAU;AAAA,MACjE;AAGA,UAAI,WAAW;AACb,eAAO,KAAK,aAAa,MAAM,SAAS,SAAS,UAAU;AAAA,MAC7D;AAGA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS,EAAE,gBAAgB,aAAa;AAAA,QACxC,MAAM;AAAA,MACR;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,WAAK,QAAQ;AAAA,QACX,+CAA+C,OAAO;AAAA,MACxD;AAEA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS,EAAE,gBAAgB,aAAa;AAAA,QACxC,MAAM,cAAc,OAAO;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,iBACZ,SACA,IACe;AACf,UAAM,OAAO,KAAK,cAAc,IAAI,OAAO,KAAK,QAAQ,QAAQ;AAChE,QAAI;AACJ,UAAM,UAAU,IAAI,QAAc,CAAC,YAAY;AAC7C,gBAAU;AAAA,IACZ,CAAC;AACD,UAAM,UAAU,KAAK;AAAA,MACnB,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AACA,SAAK,cAAc,IAAI,SAAS,OAAO;AAEvC,UAAM,KAAK,MAAM,MAAM;AAAA,IAAC,CAAC;AACzB,QAAI;AACF,YAAM,GAAG;AAAA,IACX,UAAE;AACA,cAAQ;AACR,UAAI,KAAK,cAAc,IAAI,OAAO,MAAM,SAAS;AAC/C,aAAK,cAAc,OAAO,OAAO;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,YAA6B;AACtC,WAAO,KAAK,SAAS,IAAI,UAAU;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,YAAmC;AACnD,UAAM,QAAQ,KAAK,SAAS,IAAI,UAAU;AAC1C,QAAI,OAAO;AACT,WAAK,QAAQ;AAAA,QACX,yCAAyC,UAAU;AAAA,MACrD;AACA,WAAK,SAAS,OAAO,UAAU;AAC/B,YAAM,MAAM,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAyB;AAC7B,SAAK,QAAQ,QAAQ,2CAA2C;AAEhE,QAAI,KAAK,cAAc;AACrB,oBAAc,KAAK,YAAY;AAC/B,WAAK,eAAe;AAAA,IACtB;AAEA,UAAM,eAAe,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EAAE;AAAA,MAAI,CAAC,UAC3D,MAAM,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACrC;AACA,SAAK,SAAS,MAAM;AACpB,UAAM,QAAQ,IAAI,YAAY;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,eAAuB;AACzB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKQ,cACN,SACA,YACA,YACiB;AACjB,QAAI,WAAW,QAAQ,YAAY;AAMnC,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,YAAY,kBAAkB;AAClD,YAAM,WAAW,IAAI;AAGrB,YAAM,aAAa,IAAI,gBAAgB,IAAI,YAAY;AACvD,iBAAW,OAAO,KAAK;AAGvB,iBAAW,SAAS,QAAQ,yBAAyB,CAAC,UAAU;AAC9D,cAAM,SAAS,IAAI,gBAAgB,UAAU;AAC7C,eAAO,IAAI,OAAO,KAAK;AACvB,eAAO,GAAG,QAAQ,IAAI,OAAO,SAAS,CAAC;AAAA,MACzC,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAEA,SAAK,QAAQ;AAAA,MACX,yCAAyC,UAAU,YAAY,SAAS,MAAM;AAAA,IAChF;AAEA,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,QAAQ;AAAA,MACV;AAAA,MACA,MAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,aACN,SACA,aACA,YACiB;AACjB,UAAM,UAAU,QAAQ,WAAW,WAAW;AAE9C,QAAI,CAAC,SAAS;AACZ,WAAK,QAAQ;AAAA,QACX,0CAA0C,WAAW;AAAA,MACvD;AACA,aAAO;AAAA,QACL,YAAY;AAAA,QACZ,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,QAAQ;AAAA,UACR,eAAe;AAAA,QACjB;AAAA,QACA,MAAM;AAAA,MACR;AAAA,IACF;AAEA,SAAK,QAAQ;AAAA,MACX,wCAAwC,WAAW,QAAQ,UAAU,UAAU,QAAQ,MAAM;AAAA,IAC/F;AAEA,WAAO;AAAA,MACL,YAAY;AAAA,MACZ,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,QAAQ;AAAA,QACR,kBAAkB,OAAO,QAAQ,MAAM;AAAA,MACzC;AAAA,MACA,MAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,yBAAwC;AACpD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,cAAwB,CAAC;AAE/B,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,UAAU;AACxC,UAAI,MAAM,MAAM,eAAe,KAAK,cAAc;AAChD,oBAAY,KAAK,GAAG;AAAA,MACtB;AAAA,IACF;AAEA,QAAI,CAAC,YAAY,OAAQ;AAEzB,UAAM,QAAQ;AAAA,MACZ,YAAY,IAAI,OAAO,QAAQ;AAC7B,cAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,YAAI,CAAC,MAAO;AAEZ,aAAK,QAAQ;AAAA,UACX,qDAAqD,GAAG;AAAA,QAC1D;AACA,aAAK,SAAS,OAAO,GAAG;AAExB,YAAI;AACF,gBAAM,MAAM,QAAQ,KAAK;AAAA,QAC3B,QAAQ;AAAA,QAER;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,4BACZ,QACA,WACe;AACf,UAAM,SAAmB,CAAC;AAC1B,eAAW,OAAO,KAAK,SAAS,KAAK,GAAG;AACtC,UAAI,QAAQ,aAAa,IAAI,WAAW,MAAM,EAAG,QAAO,KAAK,GAAG;AAAA,IAClE;AAEA,QAAI,CAAC,OAAO,OAAQ;AAEpB,SAAK,QAAQ;AAAA,MACX,wCAAwC,OAAO,MAAM,0BAA0B,MAAM;AAAA,IACvF;AAEA,UAAM,QAAQ;AAAA,MACZ,OAAO,IAAI,OAAO,QAAQ;AACxB,cAAM,QAAQ,KAAK,SAAS,IAAI,GAAG;AACnC,YAAI,CAAC,MAAO;AACZ,aAAK,SAAS,OAAO,GAAG;AACxB,cAAM,MAAM,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC3C,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAQO,SAAS,gBAAgB,WAI9B;AACA,QAAM,MAAM,aAAa,IAAI,YAAY;AACzC,QAAM,QAAQ,mBAAmB,KAAK,EAAE;AACxC,QAAM,oBAAoB,GAAG,SAAS,cAAc;AAEpD,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,UAAU;AAAA,EACZ;AACF;AAQO,SAAS,oBAAoB,aAA6B;AAC/D,SAAO,GAAG,WAAW,GAAG,YAAY,SAAS,GAAG,IAAI,MAAM,GAAG;AAC/D;;;ACriBO,IAAM,sBAAN,MAA0B;AAAA,EACd;AAAA,EAGA,oBAAoB,oBAAI,IAA8B;AAAA,EAC/D,YAAmC;AAAA,EACnC,YAAY;AAAA,EACZ,qBAA2C;AAAA,EAEnD,YAAY,UAAsC,CAAC,GAAG;AACpD,SAAK,UAAU;AAAA,MACb,GAAG;AAAA,MACH,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,WAAW,QAAQ,aAAa;AAAA,IAClC;AAEA,QAAI,KAAK,QAAQ,WAAW;AAC1B,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,WAAW;AAClB,WAAK,QAAQ,QAAQ,OAAO,2CAA2C;AACvE;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,SAAK,QAAQ,QAAQ;AAAA,MACnB,4DAA4D,KAAK,QAAQ,cAAc;AAAA,IACzF;AAGA,SAAK,YAAY;AAGjB,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,QAAQ,QAAQ,OAAO,uCAAuC;AACnE;AAAA,IACF;AAEA,SAAK,YAAY;AACjB,QAAI,KAAK,WAAW;AAClB,mBAAa,KAAK,SAAS;AAC3B,WAAK,YAAY;AAAA,IACnB;AAEA,SAAK,QAAQ,QAAQ,MAAM,mCAAmC;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,uBAA2C;AACzC,WAAO,MAAM,KAAK,KAAK,kBAAkB,OAAO,CAAC,EAAE;AAAA,MAAK,CAAC,GAAG,MAC1D,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAyB;AACvB,WAAO,KAAK,kBAAkB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,UAAyB;AAC7B,QAAI,KAAK,oBAAoB;AAC3B,WAAK,QAAQ,QAAQ;AAAA,QACnB;AAAA,MACF;AACA,YAAM,KAAK;AACX;AAAA,IACF;AAEA,UAAM,KAAK,YAAY;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,MAAuB;AAClC,UAAM,UAAU,KAAK,kBAAkB,OAAO,IAAI;AAClD,QAAI,SAAS;AACX,WAAK,QAAQ,QAAQ,MAAM,mCAAmC,IAAI,EAAE;AAAA,IACtE;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,eAAqB;AACnB,UAAM,QAAQ,KAAK,kBAAkB;AACrC,SAAK,kBAAkB,MAAM;AAC7B,SAAK,QAAQ,QAAQ;AAAA,MACnB,2BAA2B,KAAK;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAc,cAA6B;AACzC,UAAM,eAAe,YAAY;AAC/B,UAAI;AACF,aAAK,QAAQ,QAAQ,MAAM,kCAAkC;AAE7D,cAAM,aAAa,MAAM,uBAAuB,KAAK,OAAO;AAE5D,cAAM,aAAiC,CAAC;AACxC,cAAM,iBAAqC,CAAC;AAE5C,mBAAW,UAAU,YAAY;AAC/B,gBAAM,WAAW,KAAK,kBAAkB,IAAI,OAAO,IAAI;AACvD,cAAI,UAAU;AACZ,kBAAM,UAAU,KAAK,gBAAgB,UAAU,MAAM;AACrD,gBAAI,SAAS;AACX,6BAAe,KAAK,OAAO;AAAA,YAC7B;AAAA,UACF,OAAO;AACL,iBAAK,kBAAkB,IAAI,OAAO,MAAM,EAAE,GAAG,OAAO,CAAC;AACrD,uBAAW,KAAK,MAAM;AAAA,UACxB;AAAA,QACF;AAEA,aAAK,QAAQ,QAAQ;AAAA,UACnB,mCAAmC,WAAW,MAAM,SAAS,eAAe,MAAM,oBAAoB,KAAK,kBAAkB,IAAI;AAAA,QACnI;AAGA,mBAAW,UAAU,YAAY;AAC/B,eAAK,QAAQ,QAAQ;AAAA,YACnB,gCAAgC,OAAO,IAAI,MAAM,OAAO,SAAS,SAAS,MAAM,OAAO,QAAQ,EAAE,UAAU,OAAO,eAAe;AAAA,UACnI;AACA,cAAI;AACF,iBAAK,QAAQ,qBAAqB,MAAM;AAAA,UAC1C,QAAQ;AAAA,UAER;AAAA,QACF;AACA,mBAAW,UAAU,gBAAgB;AACnC,cAAI;AACF,iBAAK,QAAQ,kBAAkB,MAAM;AAAA,UACvC,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,cAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACjE,aAAK,QAAQ,QAAQ;AAAA,UACnB,sCAAsC,GAAG;AAAA,QAC3C;AAAA,MACF;AAAA,IACF,GAAG;AAEH,SAAK,qBAAqB;AAC1B,UAAM;AACN,SAAK,qBAAqB;AAAA,EAC5B;AAAA,EAEQ,gBACN,UACA,SACyB;AACzB,QAAI,aAAa;AAEjB,QAAI,CAAC,SAAS,SAAS,QAAQ,OAAO;AACpC,eAAS,QAAQ,QAAQ;AACzB,mBAAa;AAAA,IACf;AACA,QAAI,CAAC,SAAS,OAAO,QAAQ,KAAK;AAChC,eAAS,MAAM,QAAQ;AACvB,mBAAa;AAAA,IACf;AACA,QAAI,CAAC,SAAS,QAAQ,QAAQ,MAAM;AAClC,eAAS,OAAO,QAAQ;AACxB,mBAAa;AAAA,IACf;AACA,QAAI,CAAC,SAAS,mBAAmB,QAAQ,iBAAiB;AACxD,eAAS,kBAAkB,QAAQ;AACnC,mBAAa;AAAA,IACf;AACA,QAAI,QAAQ,YAAY,CAAC,SAAS,UAAU;AAC1C,eAAS,WAAW,QAAQ;AAC5B,mBAAa;AAAA,IACf;AACA,QAAI,QAAQ,aAAa,CAAC,SAAS,WAAW;AAC5C,eAAS,YAAY,QAAQ;AAC7B,mBAAa;AAAA,IACf;AACA,QACE,QAAQ,kBAAkB,UAC1B,SAAS,kBAAkB,QAAQ,eACnC;AACA,eAAS,gBAAgB,QAAQ;AACjC,mBAAa;AAAA,IACf;AACA,QACE,QAAQ,mBAAmB,UAC3B,SAAS,mBAAmB,QAAQ,gBACpC;AACA,eAAS,iBAAiB,QAAQ;AAClC,mBAAa;AAAA,IACf;AAEA,WAAO,aAAa,WAAW;AAAA,EACjC;AAAA,EAEQ,mBAAyB;AAC/B,QAAI,CAAC,KAAK,UAAW;AAErB,SAAK,YAAY,WAAW,MAAM;AAChC,WAAK,YAAY;AACjB,UAAI,KAAK,WAAW;AAClB,aAAK,YAAY,EAAE,QAAQ,MAAM;AAC/B,cAAI,KAAK,WAAW;AAClB,iBAAK,iBAAiB;AAAA,UACxB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,GAAG,KAAK,QAAQ,cAAc;AAAA,EAChC;AACF;;;ACkiDO,SAAS,uBACd,SACqB;AACrB,QAAM,YAAY,CAAC,QAAoC;AACrD,UAAM,MACJ,QAAQ,GAAG,KAAK,QAAQ,IAAI,YAAY,CAAC,KAAK,QAAQ,IAAI,YAAY,CAAC;AACzE,WAAO,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI;AAAA,EACvC;AAEA,SAAO;AAAA,IACL,WAAW,UAAU,YAAY,KAAK,UAAU,YAAY;AAAA,IAC5D,QAAQ,UAAU,QAAQ,KAAK,UAAU,QAAQ;AAAA,IACjD,OAAO,UAAU,OAAO,KAAK,UAAU,OAAO;AAAA,IAC9C,SAAS,UAAU,WAAW,KAAK,UAAU,WAAW;AAAA,IACxD,eACE,UAAU,kBAAkB,KAAK,UAAU,kBAAkB;AAAA,IAC/D,iBACE,UAAU,oBAAoB,KAAK,UAAU,oBAAoB;AAAA,EACrE;AACF;AAgBO,SAAS,6BACd,SACA,WACuB;AACvB,QAAM,aAAa,uBAAuB,OAAO;AAGjD,MAAI,WAAW;AACb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ,WAAW,SAAS;AAAA,MAC5B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,WAAW,aAAa,IAAI,YAAY;AACpD,QAAM,YAAY,WAAW,mBAAmB,IAC7C,YAAY,EACZ,QAAQ,MAAM,EAAE;AAGnB,QAAM,QAAQ,mBAAmB,KAAK,EAAE;AACxC,MAAI,OAAO;AACT,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,GAAG,SAAS,SAAS;AACvC,MAAI,WAAW;AACb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,GAAG,SAAS,SAAS,KAAK,aAAa;AACzD,MAAI,WAAW;AACb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,aAAa,GAAG,SAAS,QAAQ,KAAK,GAAG,SAAS,KAAK;AAC7D,QAAM,QAAQ,GAAG,SAAS,QAAQ,KAAK,aAAa;AACpD,MAAI,cAAc,CAAC,OAAO;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO;AACT,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR;AAAA,EACF;AACF;;;AC96DA,OAAO,UAAU;AACjB,SAAS,aAAa;AAsBtB,SAAS,cAAc,GAAkB,KAAqB;AAC5D,MAAI,KAAK,KAAM,QAAO;AACtB,QAAM,IAAI,OAAO,SAAS,GAAG,EAAE;AAC/B,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAEA,SAAS,eAAe,GAAkB,KAAuB;AAC/D,MAAI,KAAK,KAAM,QAAO;AACtB,QAAM,IAAI,EAAE,KAAK,EAAE,YAAY;AAC/B,MAAI,MAAM,OAAO,MAAM,UAAU,MAAM,SAAS,MAAM,IAAK,QAAO;AAClE,MAAI,MAAM,OAAO,MAAM,WAAW,MAAM,QAAQ,MAAM,IAAK,QAAO;AAClE,SAAO;AACT;AAEA,SAAS,aAAa,GAAiC;AACrD,QAAM,KAAK,KAAK,OAAO,KAAK;AAC5B,MAAI,MAAM,UAAU,MAAM,SAAS,MAAM,MAAO,QAAO;AACvD,QAAM,IAAI,MAAM,6CAA6C;AAC/D;AAEA,SAAS,mBAAmB,GAAsC;AAChE,QAAM,KAAK,KAAK,WAAW,KAAK,EAAE,YAAY;AAC9C,MAAI,MAAM,MAAM,MAAM,UAAW,QAAO;AACxC,MAAI,MAAM,YAAa,QAAO;AAC9B,MAAI,MAAM,YAAa,QAAO;AAC9B,QAAM,IAAI,MAAM,4DAA4D;AAC9E;AAEA,SAAS,eAAe,GAAwB;AAC9C,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,cAAc;AACtC,QAAM,IAAI,IAAI,KAAK,CAAC;AACpB,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC;AAC1B,UAAM,IAAI,MAAM,oCAAoC;AACtD,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAc,GAAwB;AACjE,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,WAAW,IAAI,EAAE;AACzC,QAAM,IAAI,IAAI,KAAK,CAAC;AACpB,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC;AAC1B,UAAM,IAAI,MAAM,WAAW,IAAI,wBAAwB;AACzD,SAAO;AACT;AAOO,SAAS,8BACd,MACa;AACb,QAAM,MAAM,IAAI,mBAAmB;AAAA,IACjC,GAAG,KAAK;AAAA,EACV,CAAC;AAED,QAAM,aAAa,IAAI,kBAAkB,KAAK;AAAA,IAC5C,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,CAAC;AAED,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,iBAAiB,KAAK,kBAAkB;AAG9C,QAAM,cAAc,oBAAI,IAA6B;AAErD,QAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,QAAI;AACF,UAAI,CAAC,IAAI,KAAK;AACZ,YAAI,aAAa;AACjB,YAAI,IAAI,aAAa;AACrB;AAAA,MACF;AAEA,YAAM,IAAI,IAAI,IAAI,IAAI,KAAK,UAAU,UAAU,IAAI,KAAK,UAAU,EAAE;AAEpE,UAAI,IAAI,WAAW,OAAO;AACxB,YAAI,aAAa;AACjB,YAAI,UAAU,SAAS,KAAK;AAC5B,YAAI,IAAI,oBAAoB;AAC5B;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,WAAW;AAC5B,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,UAAU,aAAa,EAAE,aAAa,IAAI,SAAS,CAAC;AAC1D,cAAM,UAAU,mBAAmB,EAAE,aAAa,IAAI,SAAS,CAAC;AAChE,YAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG;AAC5C,cAAI,aAAa;AACjB,cAAI,IAAI,iBAAiB;AACzB;AAAA,QACF;AAEA,cAAM,MAAM,GAAG,OAAO,IAAI,OAAO,IAAI,OAAO;AAC5C,cAAM,SAAS,YAAY,IAAI,GAAG;AAClC,YAAI,QAAQ;AACV,cAAI,UAAU,gBAAgB,kBAAkB;AAChD,cAAI,IAAI,KAAK,UAAU,EAAE,SAAS,OAAO,IAAI,CAAC,CAAC;AAC/C;AAAA,QACF;AAEA,cAAM,OAAO,MAAM,IAAI,iBAAiB,SAAS,SAAS;AAAA,UACxD,YAAY;AAAA,UACZ,YAAY;AAAA,UACZ,MAAM,WAAW,OAAO,IAAI,OAAO,GAAG,YAAY,YAAY,KAAK,IAAI,OAAO,EAAE;AAAA,UAChF,GAAI,YAAY,YAAY,CAAC,IAAI,EAAE,QAAQ;AAAA,QAC7C,CAAC;AAED,cAAM,UAAU,KAAK,WAAW;AAChC,oBAAY,IAAI,KAAK,EAAE,KAAK,QAAQ,CAAC;AAErC,YAAI,UAAU,gBAAgB,kBAAkB;AAChD,YAAI,IAAI,KAAK,UAAU,EAAE,QAAQ,CAAC,CAAC;AACnC;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,QAAQ;AACzB,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,IAAI,KAAK;AAC7D,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,QAAQ,KAAK;AACjE,cAAM,QAAQ,eAAe,EAAE,aAAa,IAAI,OAAO,GAAG,KAAK;AAC/D,cAAM,YAAY,eAAe,EAAE,aAAa,IAAI,WAAW,GAAG,IAAI;AACtE,cAAM,qBAAqB;AAAA,UACzB,EAAE,aAAa,IAAI,oBAAoB;AAAA,UACvC;AAAA,QACF;AACA,cAAM,WAAW,EAAE,aAAa,IAAI,KAAK,KAAK,iBAAiB,KAAK;AAEpE,YAAI,CAAC,UAAU;AACb,cAAI,aAAa;AACjB,cAAI,IAAI,kBAAkB;AAC1B;AAAA,QACF;AAEA,cAAM,aAAa,OAAO,QAAQ,MAAM,OAAO,IAAI,QAAQ;AAC3D,cAAM,qBAAqB,OAAO,QAAQ,MAAM,OAAO;AAGvD,cAAM,aAAa,UAAU,UAAU,IAAI,KAAK,UAAU,GAAG,EAAE,QAAQ,GAAG,EAAE,MAAM;AAElF,cAAM,SAAS,MAAM,WAAW,cAAc;AAAA,UAC5C;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe,OAAO;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,qBAAqB;AAAA,YACrB;AAAA,UACF;AAAA,QACF,CAAC;AAED,YAAI,aAAa,OAAO;AACxB,mBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AACnD,cAAI,UAAU,GAAG,CAAC;AAAA,QACpB;AACA,YAAI,IAAI,OAAO,IAAI;AACnB;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,aAAa;AAC9B,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,OAAO,EAAE,aAAa,IAAI,KAAK,KAAK,IAAI,KAAK;AACnD,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,IAAI,KAAK;AAC7D,cAAM,YAAY;AAAA,UAChB,EAAE,aAAa,IAAI,WAAW;AAAA,UAC9B;AAAA,QACF;AAEA,YAAI,CAAC,KAAK;AACR,cAAI,aAAa;AACjB,cAAI,IAAI,aAAa;AACrB;AAAA,QACF;AACA,YAAI,CAAC,UAAU;AACb,cAAI,aAAa;AACjB,cAAI,IAAI,kBAAkB;AAC1B;AAAA,QACF;AAEA,cAAM,MAAM,MAAM,IAAI,kBAAkB;AAAA,UACtC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,cAAM,UACJ,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,GAAG,EAAE,KAAK;AAChD,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,0BAA0B;AACxD,YAAI;AAAA,UACF;AAAA,UACA,yBAAyB,OAAO;AAAA,QAClC;AACA,YAAI,UAAU,kBAAkB,OAAO,IAAI,MAAM,CAAC;AAClD,YAAI,IAAI,GAAG;AACX;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,eAAe;AAChC,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,cACJ,EAAE,aAAa,IAAI,YAAY,KAAK,aACpC,KAAK;AACP,cAAM,QAAQ,oBAAoB,SAAS,EAAE,aAAa,IAAI,OAAO,CAAC;AACtE,cAAM,MAAM,oBAAoB,OAAO,EAAE,aAAa,IAAI,KAAK,CAAC;AAChE,cAAM,cAAc,EAAE,aAAa,IAAI,YAAY,KAAK,IAAI,KAAK;AACjE,cAAM,QAAQ,cAAc,EAAE,aAAa,IAAI,OAAO,GAAG,CAAC;AAC1D,cAAM,YAAY;AAAA,UAChB,EAAE,aAAa,IAAI,WAAW;AAAA,UAC9B;AAAA,QACF;AAEA,YAAI,eAAe,gBAAgB,eAAe,aAAa;AAC7D,cAAI,aAAa;AACjB,cAAI,IAAI,sDAAsD;AAC9D;AAAA,QACF;AAEA,YAAI,aAAa,MAAM,IAAI,cAAc;AAAA,UACvC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,UACnC;AAAA,QACF,CAAC;AAED,YAAI,QAAQ,EAAG,cAAa,WAAW,MAAM,CAAC,KAAK;AAEnD,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,kBAAkB;AAChD,YAAI;AAAA,UACF,KAAK,UAAU;AAAA,YACb;AAAA,YACA;AAAA,YACA,OAAO,MAAM,YAAY;AAAA,YACzB,KAAK,IAAI,YAAY;AAAA,YACrB;AAAA,UACF,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,iBAAiB,EAAE,aAAa,iBAAiB;AAClE,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,IAAI,KAAK;AAC7D,cAAM,cACJ,EAAE,aAAa,IAAI,YAAY,KAAK,cACpC,KAAK;AACP,cAAM,iBACJ,EAAE,aAAa,IAAI,eAAe,KAAK,OACvC,KAAK;AAEP,YAAI,CAAC,UAAU;AACb,cAAI,aAAa;AACjB,cAAI,IAAI,kBAAkB;AAC1B;AAAA,QACF;AACA,YAAI,eAAe,gBAAgB,eAAe,aAAa;AAC7D,cAAI,aAAa;AACjB,cAAI,IAAI,sDAAsD;AAC9D;AAAA,QACF;AACA,YAAI,kBAAkB,SAAS,kBAAkB,OAAO;AACtD,cAAI,aAAa;AACjB,cAAI,IAAI,4CAA4C;AACpD;AAAA,QACF;AAEA,cAAM,UAAU,MAAM,IAAI,cAAc;AAAA,UACtC;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe;AAAA,QACjB,CAAC;AAED,cAAM,UACJ,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,GAAG,EAAE,KAAK;AAEhD,YAAI,EAAE,aAAa,eAAe;AAEhC,cAAI,UAAU,KAAK;AAAA,YACjB,gBAAgB;AAAA,YAChB,iBAAiB;AAAA,YACjB,YAAY;AAAA,UACd,CAAC;AAED,gBAAMA,MAAK,MAAM,UAAU;AAAA,YACzB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAED,UAAAA,IAAG,OAAO,KAAK,GAAG;AAElB,gBAAMC,WAAU,MAAM;AACpB,YAAAD,IAAG,KAAK,SAAS;AAAA,UACnB;AACA,cAAI,GAAG,SAASC,QAAO;AACvB,cAAI,GAAG,SAASA,QAAO;AACvB,UAAAD,IAAG,GAAG,SAAS,MAAM;AACnB,gBAAI,IAAI;AAAA,UACV,CAAC;AACD;AAAA,QACF;AAGA,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,WAAW;AACzC,YAAI;AAAA,UACF;AAAA,UACA,yBAAyB,OAAO;AAAA,QAClC;AACA,YAAI,UAAU,iBAAiB,UAAU;AACzC,YAAI,UAAU,cAAc,OAAO;AAEnC,cAAM,KAAK,MAAM,UAAU;AAAA,UACzB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,WAAG,OAAO,KAAK,GAAG;AAElB,cAAM,UAAU,MAAM;AACpB,aAAG,KAAK,SAAS;AAAA,QACnB;AACA,YAAI,GAAG,SAAS,OAAO;AACvB,YAAI,GAAG,SAAS,OAAO;AACvB,WAAG,GAAG,SAAS,MAAM;AACnB,cAAI,IAAI;AAAA,QACV,CAAC;AACD;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,qBAAqB;AACtC,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,OAAO,eAAe,EAAE,aAAa,IAAI,MAAM,CAAC;AACtD,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,OAAO,KAAK;AAChE,cAAM,YAAY;AAAA,UAChB,EAAE,aAAa,IAAI,WAAW;AAAA,UAC9B;AAAA,QACF;AACA,cAAM,cAAc,cAAc,EAAE,aAAa,IAAI,aAAa,GAAG,CAAC;AAEtE,YAAI,aAAa,UAAU,aAAa,OAAO;AAC7C,cAAI,aAAa;AACjB,cAAI,IAAI,wCAAwC;AAChD;AAAA,QACF;AAEA,cAAM,EAAE,MAAM,SAAS,IAAI,MAAM,IAAI,6BAA6B;AAAA,UAChE;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,aAAa;AACjB,YAAI,UAAU,gBAAgB,YAAY;AAC1C,YAAI,UAAU,iBAAiB,UAAU;AACzC,YAAI,UAAU,sBAAsB,SAAS,QAAQ;AACrD,YAAI,UAAU,0BAA0B,OAAO,SAAS,WAAW,CAAC;AACpE,YAAI,UAAU,kBAAkB,OAAO,KAAK,MAAM,CAAC;AACnD,YAAI,IAAI,IAAI;AACZ;AAAA,MACF;AAEA,UAAI,EAAE,aAAa,sBAAsB;AACvC,cAAM,UAAU,cAAc,EAAE,aAAa,IAAI,SAAS,GAAG,CAAC;AAC9D,cAAM,YAAY,EAAE,aAAa,IAAI,UAAU,KAAK,IAAI,KAAK;AAE7D,YAAI,CAAC,UAAU;AACb,cAAI,aAAa;AACjB,cAAI,IAAI,kBAAkB;AAC1B;AAAA,QACF;AAEA,cAAM,EAAE,KAAK,KAAK,IAAI,MAAM,IAAI,+BAA+B;AAAA,UAC7D;AAAA,UACA;AAAA,QACF,CAAC;AAED,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,YAAY;AAAA,QACd,CAAC;AACD,YAAI,KAAK,GAAG;AAEZ,cAAM,UAAU,MAAM;AACpB,eAAK,KAAK;AACV,cAAI;AACF,gBAAI,QAAQ;AAAA,UACd,QAAQ;AAAA,UAER;AAAA,QACF;AACA,YAAI,GAAG,SAAS,OAAO;AACvB,YAAI,GAAG,SAAS,OAAO;AACvB,YAAI,GAAG,OAAO,MAAM;AAClB,cAAI,IAAI;AAAA,QACV,CAAC;AACD,YAAI,GAAG,SAAS,MAAM;AACpB,cAAI,IAAI;AAAA,QACV,CAAC;AACD;AAAA,MACF;AAEA,UAAI,aAAa;AACjB,UAAI,IAAI,WAAW;AAAA,IACrB,SAAS,GAAG;AACV,UAAI,aAAa;AACjB,UAAI,UAAU,gBAAgB,YAAY;AAC1C,UAAI,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AAAA,IACpD;AAAA,EACF,CAAC;AAGD,SAAO,GAAG,SAAS,MAAM;AACvB,SAAK,IAAI,MAAM,EAAE,MAAM,MAAM,MAAS;AAAA,EACxC,CAAC;AAED,SAAO;AACT;;;AC3dA,OAAOE,WAAU;AACjB,SAAS,SAAAC,cAAa;AAwBf,SAAS,sBAAsB,MAA2C;AAC/E,SAAOC,MAAK,aAAa,CAAC,KAAK,QAAQ;AACrC,QAAI,CAAC,IAAI,KAAK;AACZ,UAAI,aAAa;AACjB,UAAI,IAAI,aAAa;AACrB;AAAA,IACF;AACA,UAAM,IAAI,IAAI,IAAI,IAAI,KAAK,oBAAoB,KAAK,UAAU,EAAE;AAChE,QAAI,EAAE,aAAa,WAAW;AAC5B,UAAI,aAAa;AACjB,UAAI,IAAI,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,UAAU,OAAO,EAAE,aAAa,IAAI,SAAS,KAAK,GAAG;AAC3D,UAAM,UAAW,EAAE,aAAa,IAAI,SAAS,KAAK;AAClD,QAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG;AAC5C,UAAI,aAAa;AACjB,UAAI,IAAI,iBAAiB;AACzB;AAAA,IACF;AACA,QAAI,YAAY,UAAU,YAAY,SAAS,YAAY,OAAO;AAChE,UAAI,aAAa;AACjB,UAAI,IAAI,6CAA6C;AACrD;AAAA,IACF;AAEA,UAAM,UAAU,aAAa;AAAA,MAC3B,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf;AAAA,MACA,QAAQ;AAAA,MACR,GAAI,KAAK,aAAa,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS;AAAA,IAC/D,CAAC;AAED,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,YAAY;AAAA,IACd,CAAC;AAED,UAAM,gBAAgB,KAAK,iBAAiB;AAC5C,UAAM,KAAKC,OAAM,UAAU;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,OAAG,OAAO,KAAK,GAAG;AAClB,OAAG,OAAO,GAAG,QAAQ,MAAM;AAAA,IAE3B,CAAC;AAED,UAAM,UAAU,MAAM;AACpB,SAAG,KAAK,SAAS;AAAA,IACnB;AACA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AACvB,OAAG,GAAG,SAAS,MAAM;AACnB,UAAI,IAAI;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;;;ACjGA,OAAO,YAAY;AAkCZ,SAAS,gBACd,OACA,OACQ;AACR,MAAI,MAAM;AACV,SAAO;AACP,SAAO;AACP,SAAO;AAEP,SAAO,qBAAqB,MAAM,WAAW;AAAA;AAC7C,SAAO;AACP,SAAO,YAAY,MAAM,WAAW,IAAI,MAAM,SAAS;AAAA;AAEvD,MAAI,MAAM,cAAc,UAAU,MAAM,MAAM;AAC5C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,UAAM,MAAM,MAAM,KAAK,iBACnB,oBAAoB,MAAM,KAAK,cAAc,MAC7C;AACJ,WAAO,UAAU,MAAM,WAAW,yBAAyB,GAAG,wBAAwB,MAAM,IAAI,MAAM;AAAA;AAAA,EACxG;AAEA,MAAI,MAAM,cAAc,UAAU,MAAM,MAAM;AAC5C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,UAAM,SAAS,MAAM,KAAK,IAAI,SAAS,QAAQ;AAC/C,WAAO,UAAU,MAAM,WAAW,cAAc,MAAM,cAAc,MAAM,cAAc,MAAM;AAAA;AAAA,EAChG;AAEA,MAAI,OAAO,UAAU,OAAO;AAC1B,WAAO,qBAAqB,MAAM,WAAW;AAAA;AAC7C,WAAO;AACP,WAAO;AACP,WAAO,YAAY,MAAM,WAAW,kBAAkB,MAAM,UAAU,IAAI,MAAM,QAAQ;AAAA;AACxF,WAAO,UAAU,MAAM,WAAW,+FAA+F,MAAM,SAAS;AAAA;AAAA,EAClJ;AAEA,MAAI,OAAO,UAAU,QAAQ;AAC3B,WAAO,qBAAqB,MAAM,WAAW;AAAA;AAC7C,WAAO;AACP,WAAO;AACP,WAAO,YAAY,MAAM,WAAW,SAAS,MAAM,UAAU,IAAI,MAAM,QAAQ;AAAA;AAAA,EACjF;AAEA,SAAO;AACT;AAEO,SAAS,kBAAkB,QAA0B;AAI1D,QAAM,OAAiB,CAAC;AACxB,QAAM,MAAM,OAAO;AAEnB,QAAM,gBAAgB,CAACC,OAAsB;AAC3C,QAAIA,KAAI,KAAK,OAAO,OAAOA,EAAC,MAAM,KAAQ,OAAOA,KAAI,CAAC,MAAM,GAAM;AAChE,UAAI,OAAOA,KAAI,CAAC,MAAM,EAAM,QAAO;AACnC,UAAIA,KAAI,KAAK,OAAO,OAAOA,KAAI,CAAC,MAAM,KAAQ,OAAOA,KAAI,CAAC,MAAM;AAC9D,eAAO;AAAA,IACX;AACA,WAAO;AAAA,EACT;AAEA,MAAI,IAAI;AAER,SAAO,IAAI,KAAK;AACd,UAAM,KAAK,cAAc,CAAC;AAC1B,QAAI,GAAI;AACR;AAAA,EACF;AAEA,SAAO,IAAI,KAAK;AACd,UAAM,KAAK,cAAc,CAAC;AAC1B,QAAI,CAAC,IAAI;AACP;AACA;AAAA,IACF;AACA,UAAM,WAAW,IAAI;AACrB,QAAI,IAAI;AACR,WAAO,IAAI,KAAK;AACd,YAAM,MAAM,cAAc,CAAC;AAC3B,UAAI,IAAK;AACT;AAAA,IACF;AACA,QAAI,WAAW,GAAG;AAChB,YAAM,MAAM,OAAO,SAAS,UAAU,CAAC;AACvC,UAAI,IAAI,SAAS,EAAG,MAAK,KAAK,GAAG;AAAA,IACnC;AACA,QAAI;AAAA,EACN;AAEA,SAAO;AACT;AAEA,SAAS,0BAA0B,MAAc,UAAU,IAAY;AACrE,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,QAAM,MAAM,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC;AACtD,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK,KAAK;AAChC,QAAI,KAAK,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,EAAM;AAC9C,QAAI,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AACjC,QAAI,KAAK,IAAI,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AAAA,EAC3D;AACA,SAAO;AACT;AAEA,SAAS,4BAA4B,MAAsB;AACzD,MAAI,KAAK,UAAU,KAAK,KAAK,CAAC,MAAM,KAAQ,KAAK,CAAC,MAAM,GAAM;AAC5D,QAAI,KAAK,CAAC,MAAM,EAAM,QAAO,KAAK,SAAS,CAAC;AAC5C,QAAI,KAAK,CAAC,MAAM,KAAQ,KAAK,CAAC,MAAM,EAAM,QAAO,KAAK,SAAS,CAAC;AAAA,EAClE;AACA,SAAO;AACT;AAEA,SAAS,0BACP,MACA,YACA,QACU;AAGV,QAAM,OAAiB,CAAC;AACxB,MAAI,SAAS;AAEb,QAAM,UAAU,CAAC,KAAa,OAAuB;AACnD,QAAI,eAAe;AACjB,aAAO,WAAW,OAAO,IAAI,aAAa,EAAE,IAAI,IAAI,aAAa,EAAE;AACrE,QAAI,eAAe;AACjB,aAAO,WAAW,OAAO,IAAI,aAAa,EAAE,IAAI,IAAI,aAAa,EAAE;AACrE,QAAI,eAAe,GAAG;AACpB,YAAM,KAAK,IAAI,EAAE;AACjB,YAAM,KAAK,IAAI,KAAK,CAAC;AACrB,YAAM,KAAK,IAAI,KAAK,CAAC;AACrB,aAAO,WAAW,QACZ,MAAM,KAAO,MAAM,IAAK,QAAQ,KAChC,MAAM,KAAO,MAAM,IAAK,QAAQ;AAAA,IACxC;AACA,WAAO,IAAI,UAAU,EAAE;AAAA,EACzB;AAEA,SAAO,SAAS,cAAc,KAAK,QAAQ;AACzC,UAAM,SAAS,QAAQ,MAAM,MAAM;AACnC,cAAU;AAGV,QAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAI,SAAS,SAAS,KAAK,OAAQ,QAAO,CAAC;AAE3C,UAAM,MAAM,KAAK,SAAS,QAAQ,SAAS,MAAM;AACjD,cAAU;AACV,QAAI,IAAI,OAAQ,MAAK,KAAK,GAAG;AAAA,EAC/B;AAGA,MAAI,WAAW,KAAK,OAAQ,QAAO,CAAC;AACpC,SAAO;AACT;AAEA,SAAS,UAAU,WAAkC,MAAwB;AAC3E,MAAI,CAAC,KAAK,OAAQ,QAAO,OAAO;AAEhC,MAAI,UAAU;AACd,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,IAAI,QAAQ;AACf;AACA;AAAA,IACF;AAGA,SAAK,IAAI,CAAC,IAAK,SAAU,GAAG;AAC1B,iBAAW;AACX;AAAA,IACF;AAEA,QAAI,cAAc,QAAQ;AACxB,YAAM,IAAI,IAAI,CAAC,IAAK;AAEpB,UAAI,IAAI,KAAK,IAAI,GAAI;AAAA,IACvB,WAAW,cAAc,QAAQ;AAC/B,UAAI,IAAI,SAAS,GAAG;AAClB;AACA;AAAA,MACF;AACA,YAAM,IAAK,IAAI,CAAC,KAAM,IAAK;AAG3B,YAAM,UACH,KAAK,KAAK,KAAK,MAAQ,KAAK,MAAM,KAAK,MAAQ,KAAK,MAAM,KAAK;AAClE,UAAI,CAAC,QAAS;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,gCACP,YACA,WACU;AACV,MAAI,CAAC,YAAY,OAAQ,QAAO,CAAC;AAEjC,QAAM,aAAyB,CAAC;AAIhC,QAAM,WAAW,0BAA0B,YAAY,EAAE;AACzD,MAAI,YAAY,GAAG;AACjB,UAAM,QAAQ,aAAa,IAAI,aAAa,WAAW,SAAS,QAAQ;AACxE,UAAM,OAAO,kBAAkB,KAAK;AACpC,QAAI,KAAK,OAAQ,YAAW,KAAK,IAAI;AAAA,EACvC;AAIA,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAC9D,aAAW,KAAK,0BAA0B,YAAY,GAAG,IAAI,CAAC;AAG9D,QAAM,WAAW,4BAA4B,UAAU;AACvD,MAAI,SAAS,OAAQ,YAAW,KAAK,CAAC,QAAQ,CAAC;AAE/C,MAAI,OAAiB,CAAC;AACtB,MAAI,YAAY,OAAO;AACvB,aAAW,QAAQ,YAAY;AAC7B,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,IAAI,UAAU,WAAW,IAAI;AACnC,QAAI,IAAI,WAAW;AACjB,kBAAY;AACZ,aAAO;AACP,UAAI,cAAc,EAAG;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,mCAAmC,QAIjD;AACA,QAAM,OAAO,gCAAgC,QAAQ,MAAM;AAC3D,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,EAAG;AACpB,UAAM,UAAU,IAAI,CAAC,IAAK;AAC1B,QAAI,YAAY,GAAG;AACjB,YAAM;AACN,UAAI,IAAI,UAAU,GAAG;AACnB,cAAM,MAAM,OAAO,KAAK,CAAC,IAAI,CAAC,GAAI,IAAI,CAAC,GAAI,IAAI,CAAC,CAAE,CAAC,EAAE,SAAS,KAAK;AACnE,yBAAiB;AAAA,MACnB;AAAA,IACF,WAAW,YAAY,GAAG;AACxB,YAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,MAA+D,CAAC;AACtE,MAAI,IAAK,KAAI,MAAM;AACnB,MAAI,IAAK,KAAI,MAAM;AACnB,MAAI,eAAgB,KAAI,iBAAiB;AACzC,SAAO;AACT;AAEO,SAAS,mCAAmC,QAIjD;AACA,QAAM,OAAO,gCAAgC,QAAQ,MAAM;AAC3D,MAAI;AACJ,MAAI;AACJ,MAAI;AAEJ,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,EAAG;AACpB,UAAM,UAAW,IAAI,CAAC,KAAM,IAAK;AACjC,QAAI,YAAY,GAAI,OAAM;AAAA,aACjB,YAAY,GAAI,OAAM;AAAA,aACtB,YAAY,GAAI,OAAM;AAAA,EACjC;AAEA,QAAM,MAAoD,CAAC;AAC3D,MAAI,IAAK,KAAI,MAAM;AACnB,MAAI,IAAK,KAAI,MAAM;AACnB,MAAI,IAAK,KAAI,MAAM;AACnB,SAAO;AACT;AAEA,IAAM,iBAAiB;AAAA,EACrB;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EACtE;AAAA,EAAM;AACR;AAEO,SAAS,+BAA+B,QAIxB;AACrB,QAAM,EAAE,YAAY,SAAS,IAAI;AACjC,QAAM,kBAAkB,OAAO,mBAAmB;AAClD,QAAM,oBAAoB,eAAe,QAAQ,UAAU;AAC3D,MAAI,oBAAoB,EAAG;AAC3B,MAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,KAAK,WAAW,GAAI;AAClE,MACE,CAAC,OAAO,SAAS,eAAe,KAChC,mBAAmB,KACnB,kBAAkB;AAElB;AAGF,QAAM,OACF,kBAAkB,OAAS,MAC3B,oBAAoB,OAAS,KAC7B,WAAW,OAAS;AACxB,SAAO,IAAI,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACzC;AAEO,SAAS,gBAAgB,WAKvB;AACP,MAAI,UAAU,SAAS,EAAG,QAAO;AACjC,MAAI,UAAU,CAAC,MAAO,QAAS,UAAU,CAAC,IAAK,SAAU,IAAM,QAAO;AAEtE,QAAM,oBAAoB,UAAU,CAAC,IAAK,OAAU;AACpD,QAAM,WAAW,UAAU,CAAC,IAAK,QAAS;AAC1C,QAAM,kBAAkB,UAAU;AAClC,QAAM,qBAAqB,UAAU,CAAC,IAAK,OAAS;AACpD,QAAM,aAAa,eAAe,iBAAiB,KAAK;AACxD,QAAM,YACF,UAAU,CAAC,IAAK,MAAS,KAAO,UAAU,CAAC,IAAK,QAAS;AAE7D,MAAI,CAAC,cAAc,CAAC,SAAU,QAAO;AAErC,QAAM,eAAe,mBAAmB,IAAI;AAC5C,MAAI,UAAU,SAAS,aAAc,QAAO;AAG5C,QAAM,OACF,kBAAkB,OAAS,MAC3B,oBAAoB,OAAS,KAC7B,WAAW,OAAS;AACxB,QAAM,YAAY,IAAI,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAElD,SAAO,EAAE,cAAc,YAAY,UAAU,UAAU;AACzD;AAMA,IAAM,YAAN,MAAgB;AAAA,EAKd,YAAoB,aAAqB;AAArB;AAClB,SAAK,MAAM,OAAO,YAAY,CAAC,EAAE,aAAa,CAAC;AAG/C,SAAK,YAAY,OAAO,YAAY,CAAC,EAAE,aAAa,CAAC;AAAA,EACvD;AAAA,EATQ,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,OAAO,OAAO,YAAY,CAAC,EAAE,aAAa,CAAC;AAAA,EASnD,aAAa,IAAY;AACvB,SAAK,YAAY,OAAO;AAAA,EAC1B;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA,EAEA,iBAAiB,OAAe;AAC9B,SAAK,YAAa,KAAK,aAAa,UAAU,OAAQ;AAAA,EACxD;AAAA,EAEA,YAAY,SAAiB,QAAyB;AACpD,UAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,WAAO,CAAC,IAAI;AACZ,WAAO,CAAC,KAAK,SAAS,MAAO,KAAS,KAAK,cAAc;AACzD,WAAO,cAAc,KAAK,MAAM,OAAQ,CAAC;AACzC,WAAO,cAAc,KAAK,cAAc,GAAG,CAAC;AAC5C,WAAO,cAAc,KAAK,SAAS,GAAG,CAAC;AACvC,SAAK,MAAO,KAAK,MAAM,IAAK;AAC5B,WAAO,OAAO,OAAO,CAAC,QAAQ,OAAO,CAAC;AAAA,EACxC;AACF;AAEO,SAAS,cACd,KACA,KACA,MACA,cACA,WACU;AACV,QAAM,MAAM,KAAK;AACjB,QAAM,MAAgB,CAAC;AACvB,MAAI,IAAI,UAAU,KAAK;AACrB,QAAI,KAAK,IAAI,YAAY,KAAK,gBAAgB,SAAS,CAAC;AACxD,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,IAAI,CAAC;AAClB,QAAM,IAAI,OAAO;AACjB,QAAM,MAAM,OAAO;AACnB,QAAM,OAAO,OAAO;AACpB,QAAM,cAAc,IAAI,MAAM;AAE9B,QAAM,OAAO,IAAI,SAAS,CAAC;AAC3B,MAAI,SAAS;AACb,SAAO,SAAS,KAAK,QAAQ;AAC3B,UAAM,YAAY,KAAK,SAAS;AAChC,UAAM,WAAW,KAAK,IAAI,WAAW,MAAM,CAAC;AAC5C,UAAM,QAAQ,WAAW;AACzB,UAAM,MAAM,SAAS,YAAY,KAAK;AACtC,UAAM,YACH,QAAQ,MAAO,MAAS,MAAM,KAAO,KAAS,OAAO;AACxD,UAAM,UAAU,OAAO,OAAO;AAAA,MAC5B,OAAO,KAAK,CAAC,aAAa,QAAQ,CAAC;AAAA,MACnC,KAAK,SAAS,QAAQ,SAAS,QAAQ;AAAA,IACzC,CAAC;AACD,QAAI,KAAK,IAAI,YAAY,SAAS,gBAAgB,aAAa,GAAG,CAAC;AACnE,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAEO,SAAS,cACd,KACA,KACA,MACA,cACA,WACU;AACV,QAAM,MAAM,KAAK;AACjB,QAAM,MAAgB,CAAC;AACvB,MAAI,IAAI,UAAU,KAAK;AACrB,QAAI,KAAK,IAAI,YAAY,KAAK,gBAAgB,SAAS,CAAC;AACxD,WAAO;AAAA,EACT;AAEA,MAAI,IAAI,SAAS,EAAG,QAAO;AAE3B,QAAM,aAAa,IAAI,CAAC;AACxB,QAAM,aAAa,IAAI,CAAC;AACxB,QAAM,UAAW,cAAc,IAAK;AAGpC,QAAM,eAAgB,aAAa,MAAS,MAAM;AAClD,QAAM,eAAe;AAErB,QAAM,OAAO,IAAI,SAAS,CAAC;AAC3B,MAAI,SAAS;AACb,SAAO,SAAS,KAAK,QAAQ;AAC3B,UAAM,YAAY,KAAK,SAAS;AAChC,UAAM,WAAW,KAAK,IAAI,WAAW,MAAM,CAAC;AAC5C,UAAM,QAAQ,WAAW;AACzB,UAAM,MAAM,SAAS,YAAY,KAAK;AACtC,UAAM,YACH,QAAQ,MAAO,MAAS,MAAM,KAAO,KAAS,UAAU;AAC3D,UAAM,UAAU,OAAO,OAAO;AAAA,MAC5B,OAAO,KAAK,CAAC,cAAc,cAAc,QAAQ,CAAC;AAAA,MAClD,KAAK,SAAS,QAAQ,SAAS,QAAQ;AAAA,IACzC,CAAC;AACD,QAAI,KAAK,IAAI,YAAY,SAAS,gBAAgB,aAAa,GAAG,CAAC;AACnE,cAAU;AAAA,EACZ;AAEA,SAAO;AACT;AAEO,SAAS,sBACd,MACA,KAIA;AACA,QAAM,SAAS,gBAAgB,IAAI;AACnC,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,CAAC,EAAE;AAClC,QAAM,MAAM,KAAK,SAAS,OAAO,YAAY;AAC7C,MAAI,CAAC,IAAI,OAAQ,QAAO,EAAE,SAAS,CAAC,EAAE;AAGtC,QAAM,kBAAkB,OAAO,KAAK,CAAC,GAAM,EAAI,CAAC;AAChD,QAAM,SAAS,IAAI,SAAS;AAC5B,QAAM,WAAW,OAAO,MAAM,CAAC;AAC/B,WAAS,CAAC,IAAK,UAAU,IAAK;AAC9B,WAAS,CAAC,KAAK,SAAS,OAAS;AAEjC,QAAM,UAAU,OAAO,OAAO,CAAC,iBAAiB,UAAU,GAAG,CAAC;AAC9D,SAAO;AAAA,IACL,SAAS,CAAC,IAAI,YAAY,SAAS,IAAI,CAAC;AAAA,IACxC,QAAQ;AAAA,MACN,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,IACpB;AAAA,EACF;AACF;AAEO,SAAS,qBACd,KACA,KACuB;AACvB,MAAI,CAAC,KAAK,OAAQ,QAAO,EAAE,SAAS,CAAC,EAAE;AAGvC,QAAM,kBAAkB,OAAO,KAAK,CAAC,GAAM,EAAI,CAAC;AAChD,QAAM,SAAS,IAAI,SAAS;AAC5B,QAAM,WAAW,OAAO,MAAM,CAAC;AAC/B,WAAS,CAAC,IAAK,UAAU,IAAK;AAC9B,WAAS,CAAC,KAAK,SAAS,OAAS;AAEjC,QAAM,UAAU,OAAO,OAAO,CAAC,iBAAiB,UAAU,GAAG,CAAC;AAC9D,SAAO;AAAA,IACL,SAAS,CAAC,IAAI,YAAY,SAAS,IAAI,CAAC;AAAA,EAC1C;AACF;AAQO,IAAM,eAAN,MAAM,cAAa;AAAA,EAgDxB,YACU,QACA,kBACR,kBACA,mBAAmB,IACX,gBAAgB,MACxB;AALQ;AACA;AAGA;AAER,SAAK,WAAW,IAAI,UAAU,gBAAgB;AAC9C,QAAI,qBAAqB,QAAW;AAClC,WAAK,WAAW,IAAI,UAAU,gBAAgB;AAAA,IAChD;AACA,SAAK,yBAAyB,KAAK;AAAA,MACjC;AAAA,MACA,KAAK,MAAM,KAAK,iBAAiB,KAAK,IAAI,GAAG,gBAAgB,CAAC;AAAA,IAChE;AACA,SAAK,uBAAuB,KAAK;AAAA,MAC/B;AAAA,MACA,KAAK;AAAA,QACF,KAAK,yBAAyB,MAAa,KAAK;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAAA,EApEQ,UAAU,oBAAI,IAAmB;AAAA,EACjC,SAAS;AAAA,EAET;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA;AAAA,EACA,oBAAoB;AAAA,EACpB;AAAA,EACA;AAAA,EACA,8BAA8B;AAAA,EAC9B;AAAA,EACS,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA;AAAA,EACpB,WAAW;AAAA;AAAA,EAGpB,sBAOJ;AAAA,IACF,yBAAyB;AAAA,IACzB,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,qBAAqB;AAAA,IACrB,WAAW;AAAA,IACX,YAAY;AAAA,EACd;AAAA,EAEQ;AAAA,EACA;AAAA,EAyBR,UAAU,QAAoB;AAC5B,QAAI,KAAK,QAAQ;AACf,aAAO,QAAQ;AACf;AAAA,IACF;AAEA,UAAM,SAAwB;AAAA,MAC5B;AAAA,MACA,eAAe;AAAA,MACf,iBAAiB;AAAA,IACnB;AACA,SAAK,QAAQ,IAAI,MAAM;AAEvB,UAAM,UAAU,MAAM;AACpB,WAAK,QAAQ,OAAO,MAAM;AAC1B,UAAI;AACF,eAAO,QAAQ;AAAA,MACjB,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,WAAO,GAAG,SAAS,OAAO;AAC1B,WAAO,GAAG,SAAS,OAAO;AAAA,EAC5B;AAAA,EAEA,OAAwB,oBAAoB,OAAO,KAAK;AAAA,IACtD;AAAA,IAAM;AAAA,IAAM;AAAA,IAAM;AAAA,EACpB,CAAC;AAAA,EAED,OAAe,kBACV,MACiB;AACpB,UAAM,UAAU,KAAK,OAAO,CAAC,MAAmB,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC;AACnE,QAAI,CAAC,QAAQ,OAAQ;AACrB,UAAM,QAAkB,CAAC;AACzB,eAAW,OAAO,SAAS;AACzB,YAAM,KAAK,cAAa,mBAAmB,GAAG;AAAA,IAChD;AACA,WAAO,OAAO,OAAO,KAAK;AAAA,EAC5B;AAAA,EAEQ,mCACN,WACA,YACM;AACN,QAAI,CAAC,YAAY,OAAQ;AAEzB,QAAI,cAAc,QAAQ;AACxB,YAAM,EAAE,KAAAC,MAAK,KAAAC,KAAI,IAAI,mCAAmC,UAAU;AAClE,UAAID,QAAOC,MAAK;AACd,aAAK,4BAA4B,cAAa,eAAeD,MAAKC,IAAG;AAAA,MACvE;AACA;AAAA,IACF;AAEA,UAAM,EAAE,KAAK,KAAK,IAAI,IAAI,mCAAmC,UAAU;AACvE,QAAI,OAAO,OAAO,KAAK;AACrB,WAAK,4BAA4B,cAAa;AAAA,QAC5C;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,8BACN,WACA,QACM;AACN,QAAI,KAAK,OAAQ;AACjB,QAAI,OAAO,gBAAiB;AAE5B,UAAM,kBACJ,cAAc,SACV,KAAK,4BACL,KAAK;AACX,QAAI,CAAC,iBAAiB,OAAQ;AAE9B,UAAM,OAAO,gCAAgC,iBAAiB,SAAS;AACvE,QAAI,CAAC,KAAK,OAAQ;AAElB,UAAM,OAAgC,EAAE,eAAe,KAAK,cAAc;AAC1E,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,MAAM,KAAK,CAAC;AAClB,YAAM,YAAY,MAAM,KAAK,SAAS;AACtC,YAAM,UACJ,cAAc,SACV,cAAc,KAAK,KAAK,UAAU,MAAM,OAAO,SAAS,IACxD,cAAc,KAAK,KAAK,UAAU,MAAM,OAAO,SAAS;AAE9D,iBAAW,OAAO,SAAS;AACzB,aAAK,uBAAuB,QAAQ,GAAG;AAAA,MACzC;AAAA,IACF;AAEA,WAAO,kBAAkB;AAAA,EAC3B;AAAA,EAEA,QAAQ;AACN,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,eAAW,KAAK,MAAM,KAAK,KAAK,OAAO,GAAG;AACxC,UAAI;AACF,UAAE,OAAO,QAAQ;AAAA,MACnB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,QAAQ,MAAM;AAAA,EACrB;AAAA,EAEQ,uBAAuB,QAAuB,KAAa;AACjE,QAAI,OAAO,OAAO,aAAa,CAAC,OAAO,OAAO,SAAU;AAExD,UAAM,SAAS,OAAO,MAAM,CAAC;AAC7B,WAAO,cAAc,IAAI,SAAS,OAAQ,CAAC;AAC3C,UAAM,SAAS,OAAO,OAAO,CAAC,QAAQ,GAAG,CAAC;AAE1C,QAAI;AACF,aAAO,OAAO,MAAM,MAAM;AAAA,IAC5B,QAAQ;AACN,UAAI;AACF,eAAO,OAAO,QAAQ;AAAA,MACxB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,wBACN,KACA,WACA;AACA,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,CAAC,UAAU,CAAC,EAAG;AACnB,WAAK,uBAAuB,GAAG,GAAG;AAAA,IACpC;AAAA,EACF;AAAA,EAEQ,6BAAmC;AAEzC,SAAK,iBAAiB;AACtB,SAAK,oBAAoB;AACzB,SAAK,iBAAiB;AACtB,SAAK,kBAAkB;AACvB,SAAK,8BAA8B;AACnC,SAAK,6BAA6B;AAAA,EACpC;AAAA,EAEQ,eACN,MACA,SACM;AACN,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa;AACnB,UAAM,QAAQ,KAAK;AACnB,QAAI,SAAS,mBAAmB;AAC9B,YAAM;AACN,UAAI,MAAM,MAAM,0BAA0B,WAAY;AACtD,YAAM,0BAA0B;AAChC,WAAK,OAAO;AAAA,QACV,2BAA2B,OAAO,yBAAyB,MAAM,mBAAmB;AAAA,MACtF;AACA;AAAA,IACF;AACA,QAAI,SAAS,QAAQ;AACnB,YAAM;AACN,UAAI,MAAM,MAAM,gBAAgB,WAAY;AAC5C,YAAM,gBAAgB;AACtB,WAAK,OAAO;AAAA,QACV,2BAA2B,OAAO,eAAe,MAAM,SAAS;AAAA,MAClE;AACA;AAAA,IACF;AACA,UAAM;AACN,QAAI,MAAM,MAAM,iBAAiB,WAAY;AAC7C,UAAM,iBAAiB;AACvB,SAAK,OAAO;AAAA,MACV,2BAA2B,OAAO,gBAAgB,MAAM,UAAU;AAAA,IACpE;AAAA,EACF;AAAA,EAEQ,gCAAsC;AAC5C,SAAK,SAAS,iBAAiB,KAAK,sBAAsB;AAAA,EAC5D;AAAA,EAEA,kCACE,mBACA;AACA,QAAI,sBAAsB,QAAQ,sBAAsB,QAAW;AACjE,WAAK,8BAA8B;AACnC;AAAA,IACF;AACA,QAAI,CAAC,OAAO,SAAS,iBAAiB,GAAG;AACvC,WAAK,8BAA8B;AACnC;AAAA,IACF;AAEA,UAAM,WAAY,sBAAsB;AAExC,QAAI,KAAK,mBAAmB,QAAW;AACrC,YAAM,YAAY,KAAK;AACvB,UAAI,WAAW,WAAW;AAExB,cAAM,aAAa,YAAY,cAAc,WAAW;AACxD,YAAI,YAAY;AACd,eAAK,qBAAqB;AAC1B,eAAK;AAAA,YACH;AAAA,YACA,gCAAgC,SAAS,aAAa,QAAQ,eAAe,KAAK,iBAAiB;AAAA,UACrG;AAAA,QACF,OAAO;AAEL,eAAK;AAAA,YACH;AAAA,YACA,yDAAyD,SAAS,aAAa,QAAQ;AAAA,UACzF;AACA,eAAK,2BAA2B;AAAA,QAClC;AAAA,MACF;AAAA,IACF;AAEA,SAAK,iBAAiB;AACtB,UAAM,QAAQ,KAAK,oBAAoB;AAEvC,QAAI,KAAK,mBAAmB,QAAW;AACrC,WAAK,iBAAiB;AACtB,UAAI,KAAK,oBAAoB;AAC3B,aAAK,kBAAkB,KAAK;AAC9B;AAAA,IACF;AAEA,UAAM,YAAY,KAAK;AACvB,UAAM,UAAU,QAAQ;AACxB,SAAK,iBAAiB;AAWtB,QAAI;AACJ,QAAI,CAAC,OAAO,SAAS,OAAO,KAAK,UAAU,GAAG;AAC5C,WAAK;AAAA,QACH;AAAA,QACA,qBAAqB,OAAO,WAAW,KAAK,cAAc,SAAS,eAAe,KAAK,mBAAmB,KAAK;AAAA,MACjH;AACA,WAAK,8BAA8B;AACnC,yBACE,KAAK,8BAA8B,KAAK;AAAA,IAC5C,WAAW,YAAY,GAAG;AACxB,WAAK;AAEL,yBACE,KAAK,8BAA8B,KAAK;AAAA,IAC5C,WAAW,WAAW,KAAK,mBAAmB;AAC5C,UAAI,KAAK,8BAA8B,GAAG;AACxC,cAAM,gBAAgB,KAAK,8BAA8B;AACzD,cAAM,kBAAkB,KAAK;AAAA,UAC3B;AAAA,UACA,KAAK,MAAM,UAAU,aAAa;AAAA,QACpC;AACA,aAAK,6BAA6B;AAClC,aAAK,8BAA8B;AAEnC,cAAM,UAAU,KAAK,mBAAmB;AACxC,aAAK,kBACH,WAAW,kBAAkB,WAAW,KAAK;AAC/C,2BAAmB;AAAA,MACrB,OAAO;AACL,cAAM,UAAU,KAAK,mBAAmB;AACxC,aAAK,kBAAkB,WAAW,UAAU,WAAW,KAAK;AAC5D,2BAAmB;AAAA,MACrB;AAAA,IACF,OAAO;AAEL,WAAK;AAAA,QACH;AAAA,QACA,qBAAqB,OAAO,WAAW,KAAK,cAAc,SAAS,eAAe,KAAK,mBAAmB,KAAK,cAAc,KAAK,2BAA2B;AAAA,MAC/J;AACA,WAAK,8BAA8B;AACnC,yBACE,KAAK,8BAA8B,KAAK;AAAA,IAC5C;AAEA,UAAM,MAAM,KAAK;AAAA,MACf;AAAA,MACA,KAAK,MAAO,mBAAmB,KAAK,iBAAkB,GAAS;AAAA,IACjE;AACA,SAAK,SAAS,iBAAiB,GAAG;AAAA,EACpC;AAAA,EAEA,oBACE,WACA,kBACA,YACA,cACA;AACA,QAAI,KAAK,OAAQ;AAIjB,SAAK,mCAAmC,WAAW,gBAAgB;AAEnE,UAAM,OAAO,gCAAgC,kBAAkB,SAAS;AACxE,QAAI,CAAC,KAAK,OAAQ;AAIlB,QAAI,kBAAkB;AACtB,QAAI,cAAc,QAAQ;AACxB,iBAAW,OAAO,MAAM;AACtB,cAAM,IAAI,IAAI,CAAC,IAAK;AACpB,YAAI,MAAM,GAAG;AAEX,4BAAkB;AAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF,OAAO;AACL,iBAAW,OAAO,MAAM;AACtB,YAAI,IAAI,SAAS,EAAG;AACpB,cAAM,IAAK,IAAI,CAAC,KAAM,IAAK;AAE3B,YAAI,KAAK,MAAM,KAAK,IAAI;AACtB,4BAAkB;AAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,oBAAoB,cAAc;AAIxC,UAAM,eAAe,CAAC,MACpB,oBAAoB,OAAO,CAAC,EAAE;AAChC,QAAI,eAAe;AACnB,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,aAAa,CAAC,GAAG;AACnB,uBAAe;AACf;AAAA,MACF;AAAA,IACF;AAGA,QAAI,CAAC,cAAc;AACjB,iBAAW,KAAK,KAAK,SAAS;AAC5B,YAAI,CAAC,EAAE,cAAe;AACtB,aAAK,8BAA8B,WAAW,CAAC;AAAA,MACjD;AACA;AAAA,IACF;AAEA,SAAK,kCAAkC,YAAY;AAGnD,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,CAAC,EAAE,cAAe;AACtB,WAAK,8BAA8B,WAAW,CAAC;AAAA,IACjD;AAEA,UAAM,OAAgC,EAAE,eAAe,KAAK,cAAc;AAC1E,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAM,MAAM,KAAK,CAAC;AAClB,YAAM,YAAY,MAAM,KAAK,SAAS;AACtC,YAAM,UACJ,cAAc,SACV,cAAc,KAAK,KAAK,UAAU,MAAM,MAAM,SAAS,IACvD,cAAc,KAAK,KAAK,UAAU,MAAM,MAAM,SAAS;AAE7D,iBAAW,OAAO;AAChB,aAAK,wBAAwB,KAAK,YAAY;AAAA,IAClD;AAGA,QAAI,mBAAmB;AACrB,iBAAW,KAAK,KAAK,QAAS,GAAE,gBAAgB;AAAA,IAClD;AAAA,EACF;AAAA,EAEA,mBAAmB,MAEjB;AACA,QAAI,KAAK,OAAQ,QAAO,CAAC;AACzB,QAAI,CAAC,KAAK,SAAU,QAAO,CAAC;AAE5B,UAAM,EAAE,SAAS,OAAO,IAAI,sBAAsB,MAAM,KAAK,QAAQ;AAErE,eAAW,OAAO;AAChB,WAAK,wBAAwB,KAAK,CAAC,MAAM,CAAC,EAAE,aAAa;AAG3D,QAAI,QAAQ,OAAQ,MAAK,SAAS,iBAAiB,IAAI;AAEvD,WAAO,SAAS,EAAE,QAAQ,OAAO,IAAI,CAAC;AAAA,EACxC;AAAA,EAEA,qBAAqB,KAAmB;AACtC,QAAI,KAAK,OAAQ;AACjB,QAAI,CAAC,KAAK,SAAU;AAEpB,UAAM,EAAE,QAAQ,IAAI,qBAAqB,KAAK,KAAK,QAAQ;AAE3D,eAAW,OAAO;AAChB,WAAK,wBAAwB,KAAK,CAAC,MAAM,CAAC,EAAE,aAAa;AAG3D,QAAI,QAAQ,OAAQ,MAAK,SAAS,iBAAiB,IAAI;AAAA,EACzD;AAAA,EAEA,mBAAmB,WAAyB;AAC1C,QAAI,KAAK,OAAQ;AACjB,QAAI,CAAC,aAAa,UAAU,SAAS,GAAI;AACzC,UAAM,UAAW,UAAU,CAAC,KAAM,IAAK;AACvC,QAAI,YAAY,EAAG;AAGnB,SAAK,wBAAwB,WAAW,CAAC,MAAM,CAAC,EAAE,aAAa;AAAA,EACjE;AAAA,EAEA,OAAe,gBAAgB,QAAoC;AACjE,QAAI,CAAC,UAAU,OAAO,SAAS,GAAI;AACnC,UAAM,UAAW,OAAO,CAAC,KAAM,IAAK;AACpC,QAAI,YAAY,EAAG;AAEnB,UAAM,WAAW,OAAO,CAAC,IAAK,QAAU;AACxC,UAAM,aAAa,OAAO,CAAC,IAAK,QAAU;AAC1C,UAAM,YAAY,OAAO,CAAC,IAAK;AAC/B,QAAI,SAAS,KAAK,YAAY;AAC9B,QAAI,SAAS,OAAO,OAAQ;AAE5B,QAAI,WAAW;AACb,UAAI,SAAS,IAAI,OAAO,OAAQ;AAChC,YAAM,cAAc,OAAO,aAAa,SAAS,CAAC;AAClD,gBAAU,IAAI,cAAc;AAC5B,UAAI,SAAS,OAAO,OAAQ;AAAA,IAC9B;AAEA,QAAI,MAAM,OAAO;AACjB,QAAI,SAAS;AACX,YAAM,SAAS,OAAO,OAAO,SAAS,CAAC;AACvC,UAAI,UAAU,KAAK,SAAS,OAAO,OAAQ;AAC3C,YAAM,OAAO,SAAS;AACtB,UAAI,MAAM,OAAQ;AAAA,IACpB;AAEA,QAAI,OAAO,OAAQ;AACnB,WAAO,OAAO,SAAS,QAAQ,GAAG;AAAA,EACpC;AAAA,EAEA,OAAe,4BAA4B,QAAyB;AAClE,UAAM,UAAU,cAAa,gBAAgB,MAAM;AACnD,QAAI,CAAC,WAAW,QAAQ,SAAS,EAAG,QAAO;AAE3C,UAAM,UAAU,QAAQ,CAAC,IAAK;AAG9B,QAAI,WAAW,KAAK,WAAW,IAAI;AACjC,aAAO,YAAY,KAAK,YAAY,KAAK,YAAY;AAAA,IACvD;AAGA,QAAI,YAAY,IAAI;AAClB,UAAI,SAAS;AACb,aAAO,SAAS,KAAK,QAAQ,QAAQ;AACnC,cAAM,OAAO,QAAQ,aAAa,MAAM;AACxC,kBAAU;AACV,YAAI,CAAC,QAAQ,SAAS,OAAO,QAAQ,OAAQ;AAC7C,cAAM,IAAI,QAAQ,MAAM,IAAK;AAC7B,YAAI,MAAM,KAAK,MAAM,KAAK,MAAM,EAAG,QAAO;AAC1C,kBAAU;AAAA,MACZ;AACA,aAAO;AAAA,IACT;AAGA,QAAI,YAAY,MAAM,QAAQ,UAAU,GAAG;AACzC,YAAM,WAAW,QAAQ,CAAC;AAC1B,YAAM,SAAS,WAAW,SAAU;AACpC,YAAM,WAAW,WAAW;AAC5B,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO,aAAa,KAAK,aAAa,KAAK,aAAa;AAAA,IAC1D;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,OAAe,4BAA4B,QAAyB;AAClE,UAAM,UAAU,cAAa,gBAAgB,MAAM;AACnD,QAAI,CAAC,WAAW,QAAQ,SAAS,EAAG,QAAO;AAE3C,UAAM,UAAW,QAAQ,CAAC,KAAM,IAAK;AAErC,UAAM,eAAe,CAAC,MAAc;AAElC,UAAI,KAAK,MAAM,KAAK,GAAI,QAAO;AAC/B,UAAI,MAAM,MAAM,MAAM,MAAM,MAAM,GAAI,QAAO;AAC7C,aAAO;AAAA,IACT;AAGA,QAAI,YAAY,MAAM,QAAQ,UAAU,GAAG;AACzC,YAAM,WAAW,QAAQ,CAAC;AAC1B,YAAM,SAAS,WAAW,SAAU;AACpC,YAAM,WAAW,WAAW;AAC5B,UAAI,CAAC,MAAO,QAAO;AACnB,aAAO,aAAa,QAAQ;AAAA,IAC9B;AAEA,WAAO,aAAa,OAAO;AAAA,EAC7B;AAAA,EAEA,mBAAmB,WAAsB,WAAyB;AAChE,QAAI,KAAK,OAAQ;AACjB,QAAI,CAAC,aAAa,UAAU,SAAS,GAAI;AACzC,UAAM,UAAW,UAAU,CAAC,KAAM,IAAK;AACvC,QAAI,YAAY,EAAG;AAEnB,UAAM,iBACJ,cAAc,SACV,cAAa,4BAA4B,SAAS,IAClD,cAAa,4BAA4B,SAAS;AAExD,UAAM,eAAe,CAAC,MACpB,iBAAiB,OAAO,CAAC,EAAE;AAC7B,SAAK,wBAAwB,WAAW,YAAY;AAEpD,QAAI,gBAAgB;AAClB,iBAAW,KAAK,KAAK,QAAS,GAAE,gBAAgB;AAAA,IAClD;AAAA,EACF;AACF;;;AC3pCA,OAAO,aAAa;;;ACQpB,SAAS,SAAAC,cAAa;AACtB,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAkH7B,SAAS,yBACP,UACA,WACA,YACA,UACA,WACA,QAC0B;AAC1B,QAAM,OAAO,KAAK,MAAM,QAAQ;AAChC,QAAM,OAAO,KAAK,MAAM,SAAS;AACjC,QAAM,IAAI;AAEV,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,IACtB,KAAK;AACH,aAAO,EAAE,GAAG,YAAY,OAAO,GAAG,GAAG,EAAE;AAAA,IACzC,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,GAAG,aAAa,OAAO,EAAE;AAAA,IAC1C,KAAK;AACH,aAAO,EAAE,GAAG,YAAY,OAAO,GAAG,GAAG,aAAa,OAAO,EAAE;AAAA,IAC7D,KAAK;AACH,aAAO,EAAE,GAAG,KAAK,OAAO,YAAY,QAAQ,CAAC,GAAG,GAAG,KAAK,OAAO,aAAa,QAAQ,CAAC,EAAE;AAAA,IACzF,KAAK;AACH,aAAO,EAAE,GAAG,KAAK,OAAO,YAAY,QAAQ,CAAC,GAAG,GAAG,EAAE;AAAA,IACvD,KAAK;AACH,aAAO,EAAE,GAAG,KAAK,OAAO,YAAY,QAAQ,CAAC,GAAG,GAAG,aAAa,OAAO,EAAE;AAAA,IAC3E,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,GAAG,KAAK,OAAO,aAAa,QAAQ,CAAC,EAAE;AAAA,IACxD,KAAK;AACH,aAAO,EAAE,GAAG,YAAY,OAAO,GAAG,GAAG,KAAK,OAAO,aAAa,QAAQ,CAAC,EAAE;AAAA,IAC3E;AACE,aAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,EACxB;AACF;AAKO,IAAM,kBAAN,cAA8B,aAKlC;AAAA,EACO;AAAA,EACA,cAAyD;AAAA,EACzD,aAAwD;AAAA,EACxD,gBAAiD;AAAA,EACjD,SAAS;AAAA,EACT;AAAA,EAEA,YAA+B;AAAA;AAAA;AAAA,EAI/B,eAAuB,OAAO,MAAM,CAAC;AAAA;AAAA,EAGrC;AAAA,EAGA;AAAA,EAIA;AAAA,EAEA,0BAA0B;AAAA,EAC1B,uBAAuB;AAAA,EAEvB;AAAA,EAEA;AAAA,EACA;AAAA,EAEA,gBAAgB,UAAe,WAAyC;AAC9E,QAAI;AACF,YAAM,SAAS,CAAC,SAAkB,OAAO,IAAI,YAAY,EAAE,SAAS,KAAK;AACzE,YAAM,UAAiB,MAAM,QAAQ,UAAU,OAAO,IAAI,SAAS,UAAU,CAAC;AAC9E,YAAM,YAAY,oBAAI,IAAiB;AACvC,iBAAW,KAAK,QAAS,WAAU,IAAI,EAAE,SAAS,CAAC;AAEnD,UAAI,OAAO,UAAU,IAAI,SAAS,GAAG,YAAY,EAAG,QAAO;AAE3D,YAAM,iBAAkC,CAAC,OAAO,QAAQ,KAAK;AAC7D,iBAAW,KAAK,gBAAgB;AAC9B,cAAM,KAAK,UAAU,IAAI,CAAC;AAC1B,YAAI,MAAM,OAAO,GAAG,YAAY,EAAG,QAAO;AAAA,MAC5C;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,KAA+D;AACpF,QAAI,CAAC,IAAK,QAAO,QAAQ,QAAQ;AACjC,UAAM,IAAK,IAAY;AACvB,QAAI,OAAO,MAAM,WAAY,QAAO,QAAQ,QAAQ;AACpD,QAAI;AACF,aAAO,QAAQ,QAAQ,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,MAAM,MAAS,EAAE,MAAM,MAAM,MAAS;AAAA,IACjF,QAAQ;AACN,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,YAAY,SAAiC;AAC3C,UAAM;AACN,SAAK,UAAU;AAAA,MACb,aAAa;AAAA,MACb,SAAS;AAAA,MACT,WAAW;AAAA,MACX,GAAG;AAAA,IACL;AACA,SAAK,SAAS,QAAQ,UAAU;AAAA,EAClC;AAAA,EAEQ,mBAAmB,WAAmB,YAA4B;AACxE,UAAM,MAAM,KAAK,QAAQ;AACzB,QAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO;AAC9C,UAAM,IAAI,OAAO,GAAG;AACpB,QAAI,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,EAAG,QAAO;AAEzC,QAAI,IAAI,EAAG,QAAO,KAAK,MAAM,CAAC;AAE9B,UAAM,OAAO,KAAK,IAAI,WAAW,UAAU;AAC3C,WAAO,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,CAAC;AAAA,EACzC;AAAA,EAEQ,kBAAkB,KAAwG;AAChI,UAAM,IAAS,MAAO,IAAY,SAAS;AAC3C,QAAI,CAAC,EAAG,QAAO,CAAC;AAChB,QAAI;AACF,YAAM,YAAY,OAAO,EAAE,iBAAiB,aAAa,EAAE,aAAa,IAAI;AAC5E,YAAM,OAAO,OAAO,EAAE,YAAY,aAAa,EAAE,QAAQ,IAAI;AAC7D,YAAM,MAAM,OAAO,EAAE,uBAAuB,aAAa,EAAE,mBAAmB,IAAI;AAClF,YAAM,MAAM,OAAO,EAAE,gBAAgB,aAAa,EAAE,YAAY,IAAI;AACpE,aAAO,EAAE,WAAW,MAAM,KAAK,IAAI;AAAA,IACrC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEQ,iBAAiB,KAAyF;AAChH,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,OAAO,IAAI,SAAS,GAAG,KAAK,IAAI,IAAI,IAAI,MAAM,CAAC,EAAE,SAAS,KAAK;AACrE,UAAM,QAAQ,IAAI,SAAS,GAAG,KAAK,IAAI,KAAK,IAAI,MAAM,CAAC;AACvD,UAAM,OAAO,WAAW,MAAM,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AAC1D,WAAO,EAAE,KAAK,IAAI,QAAQ,SAAS,MAAM,UAAU,KAAK;AAAA,EAC1D;AAAA,EAEA,MAAc,eACZ,KACA,WACA,iBACwH;AACxH,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI;AAEJ,WAAO,KAAK,IAAI,IAAI,QAAQ,WAAW;AACrC,YAAM,IAAI,MAAM,QAAQ,KAAK;AAAA,QAC3B,IAAI,KAAK;AAAA,QACT,IAAI,QAA6B,CAAC,YAAY,WAAW,MAAM,QAAQ,EAAE,OAAO,QAAW,MAAM,MAAM,CAAQ,GAAG,GAAG,CAAC;AAAA,MACxH,CAAC;AAED,UAAI,CAAC,KAAM,EAAU,KAAM,QAAO;AAClC,YAAM,IAAK,EAAU;AACrB,UAAI,CAAC,KAAK,EAAE,MAAO;AACnB,UAAI,CAAC,MAAO,SAAQ,EAAE,MAAM,EAAE,MAAM,WAAW,EAAE,WAAW,YAAY,EAAE,YAAY,cAAc,EAAE,aAAa;AACnH,UAAI,EAAE,WAAY,QAAO,EAAE,MAAM,EAAE,MAAM,WAAW,EAAE,WAAW,YAAY,EAAE,YAAY,cAAc,EAAE,aAAa;AAAA,IAC1H;AAEA,WAAO,kBAAkB,SAAY;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,iCAAiC;AAAA,IACnD;AAEA,SAAK,SAAS;AACd,SAAK,cAAc;AACnB,SAAK,uBAAuB;AAC5B,SAAK,OAAO,MAAM,gDAAgD;AAElE,QAAI;AACF,YAAM,WAAW,KAAK,QAAQ,YAAY,KAAK,QAAQ;AACvD,YAAM,UAAU,KAAK,QAAQ,WAAW,KAAK,QAAQ;AAErD,YAAM,kBAAkB,KAAK,kBAAkB,QAAQ;AACvD,YAAM,iBAAiB,KAAK,kBAAkB,OAAO;AACrD,YAAM,aAAc,UAAkB,UAAW,SAAiB,SAAU,SAAiB,WAAY,QAAgB,SAAS;AAClI,WAAK,OAAO;AAAA,QACV,sCAAsC,KAAK,QAAQ,YAAY,aAAa,KAAK,QAAQ,YAAY,aAAa,KAAK,QAAQ,WAAW,aAAa,KAAK,QAAQ,WAAW,WACpK,QAAQ,KAAK,QAAQ,KAAK,CAAC,cAAc,QAAQ,KAAK,QAAQ,SAAS,CAAC,eACnE,UAAU,gBACT,KAAK,UAAU,eAAe,CAAC,eAAe,KAAK,UAAU,cAAc,CAAC;AAAA,MAC/F;AAGA,YAAM,gBAAgB,MAAM,SAAS,kBAAkB,KAAK,QAAQ,YAAY;AAChF,YAAM,eAAe,MAAM,QAAQ,kBAAkB,KAAK,QAAQ,WAAW;AAE7E,YAAM,YAAY,KAAK,QAAQ,cAAc;AAC7C,YAAM,eAAe,YACjB,KAAK,gBAAgB,eAAe,KAAK,QAAQ,YAAY,IAC7D,KAAK,QAAQ;AACjB,YAAM,cAAc,YAChB,KAAK,gBAAgB,cAAc,KAAK,QAAQ,WAAW,IAC3D,KAAK,QAAQ;AAEjB,WAAK,wBAAwB;AAC7B,WAAK,uBAAuB;AAE5B,YAAM,kBAAkB,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,YAAY;AACpF,YAAM,iBAAiB,aAAa,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,WAAW;AAEjF,UAAI,CAAC,mBAAmB,CAAC,gBAAgB;AACvC,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC7C;AAEA,YAAM,YAAY,gBAAgB;AAClC,YAAM,aAAa,gBAAgB;AAGnC,YAAM,sBAAsB,KAAK,QAAQ,WAAW;AACpD,YAAM,UAAU,KAAK,IAAI,KAAK,KAAK,IAAI,MAAM,OAAO,mBAAmB,CAAC,CAAC;AACzE,YAAM,aAAa,eAAe,QAAQ,KAAK,eAAe,SAAS,IACnE,eAAe,QAAQ,eAAe,SACtC,KAAK;AAET,UAAI,WAAW,KAAK,MAAM,YAAY,OAAO;AAC7C,UAAI,YAAY,KAAK,MAAM,WAAW,UAAU;AAGhD,YAAM,eAAe,KAAK,MAAM,aAAa,OAAO;AACpD,UAAI,YAAY,cAAc;AAC5B,oBAAY;AACZ,mBAAW,KAAK,MAAM,YAAY,UAAU;AAAA,MAC9C;AAGA,iBAAW,KAAK,IAAI,GAAG,WAAY,WAAW,CAAE;AAChD,kBAAY,KAAK,IAAI,GAAG,YAAa,YAAY,CAAE;AAEnD,YAAM,WAAW;AAAA,QACf,KAAK,QAAQ,eAAe;AAAA,QAC5B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,mBAAmB,WAAW,UAAU;AAAA,MAC/C;AAEA,WAAK,OAAO;AAAA,QACV,2BAA2B,SAAS,IAAI,UAAU,UAAU,QAAQ,IAAI,SAAS,aACnE,OAAO,cAAc,KAAK,QAAQ,eAAe,cAAc,YAAY,KAAK,QAAQ,aAAa,EAAE,SAC5G,SAAS,CAAC,KAAK,SAAS,CAAC;AAAA,MACpC;AAEA,YAAM,eAAe,KAAK,QAAQ;AAClC,YAAM,cAAc,KAAK,QAAQ;AAEjC,UAAI,gBAAgB,aAAa;AAC/B,aAAK,YAAY;AAIjB,YAAI,CAAC,KAAK,QAAQ,kBAAkB;AAClC,gBAAM,SAAS,CAAC,SAAkB,OAAO,IAAI,YAAY,EAAE,SAAS,KAAK;AACzE,gBAAM,WAAW,iBAAiB;AAClC,gBAAM,UAAU,gBAAgB;AAChC,cAAI,CAAC,OAAO,QAAQ,KAAK,CAAC,OAAO,OAAO,GAAG;AACzC,kBAAM,IAAI;AAAA,cACR,qEACoB,YAAY,SAAS,SAAS,WAAW,SAAS;AAAA,YAExE;AAAA,UACF;AAAA,QACF;AAEA,cAAM,KAAK;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,QAAQ,iBAAiB;AAAA,QAChC;AAEA,aAAK,OAAO,MAAM,0DAA0D;AAC5E;AAAA,MACF;AAEA,WAAK,YAAY;AAKjB,YAAM,6BACJ,CAAC,CAAC,KAAK,QAAQ,SAAS,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;AAGpE,WAAK,cAAc,mBAAmB,UAAU,KAAK,QAAQ,cAAc,YAAY;AACvF,WAAK,aAAa,6BACd,mBAAmB,SAAS,KAAK,QAAQ,aAAa,aAAa,EAAE,SAAS,YAAY,CAAC,IAC3F,mBAAmB,SAAS,KAAK,QAAQ,aAAa,WAAW;AAKrE,YAAM,wBAAwB,KAAK,IAAI,KAAM,KAAK,QAAQ,yBAAyB,GAAM;AAEzF,YAAM,2BAA2B,KAAK,QAAQ,mBAAmB,QAAS,KAAK,QAAQ,4BAA4B;AAInH,YAAM,kBAAkB;AACxB,YAAM,kBAAkB,KAAK,IAAI,KAAO,KAAK,IAAI,KAAO,KAAK,MAAM,wBAAwB,CAAC,CAAC,CAAC;AAE9F,UAAI,0BAA0B;AAC5B,cAAM,CAAC,YAAY,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,UAChD,KAAK,eAAe,KAAK,aAAa,iBAAiB,IAAI;AAAA,UAC3D,KAAK,eAAe,KAAK,YAAY,iBAAiB,IAAI;AAAA,QAC5D,CAAC;AACD,aAAK,kBAAkB;AACvB,aAAK,iBAAiB;AAAA,MACxB,OAAO;AACL,cAAM,CAAC,YAAY,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,UAChD,KAAK,eAAe,KAAK,aAAa,iBAAiB,KAAK;AAAA,UAC5D,KAAK,eAAe,KAAK,YAAY,iBAAiB,KAAK;AAAA,QAC7D,CAAC;AACD,aAAK,kBAAkB;AACvB,aAAK,iBAAiB;AAAA,MACxB;AAEA,YAAM,UAAU,KAAK,iBAAiB,KAAK,iBAAiB,IAAI;AAChE,YAAM,SAAS,KAAK,iBAAiB,KAAK,gBAAgB,IAAI;AAC9D,UAAI,WAAW,QAAQ;AACrB,cAAM,OAAO,QAAQ,aAAa,OAAO;AACzC,aAAK,OAAO;AAAA,UACV,+CAA+C,KAAK,UAAU,OAAO,CAAC,SAAS,KAAK,UAAU,MAAM,CAAC,SAAS,IAAI;AAAA,QACpH;AACA,YAAI,MAAM;AACR,eAAK,OAAO;AAAA,YACV;AAAA,UAEF;AAAA,QACF;AAAA,MACF;AAEA,WAAK,OAAO;AAAA,QACV,kCAAkC,KAAK,iBAAiB,aAAa,aAAc,KAAK,kBAAkB,UAAU,MAAO,UAAU,KAAK,gBAAgB,aAAa,aAAc,KAAK,iBAAiB,UAAU,MAAO;AAAA,MAC9N;AAIA,UAAI,CAAC,KAAK,mBAAmB,CAAC,KAAK,gBAAgB;AACjD,cAAM,UAAU;AAAA,UACd,CAAC,KAAK,kBAAkB,UAAU;AAAA,UAClC,CAAC,KAAK,iBAAiB,SAAS;AAAA,QAClC,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAC3B,cAAM,IAAI;AAAA,UACR,2CAA2C,OAAO,YAAY,qBAAqB;AAAA,QAGrF;AAAA,MACF;AAIA,UAAI,CAAC,KAAK,QAAQ,kBAAkB;AAClC,YAAI;AACF,gBAAM,MAAM,KAAK,gBAAgB;AACjC,gBAAM,MAAM,KAAK,eAAe;AAChC,cAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,KAAK,OAAO,SAAS,GAAG,GAAG;AACtG,kBAAM,YAAY,MAAM,OAAO;AAC/B,kBAAM,MAAM,KAAK,IAAI,QAAQ;AAE7B,gBAAI,OAAO,OAAO,OAAO,IAAI;AAC3B,kBAAI,WAAW,GAAG;AAEhB,qBAAK,uBAAuB,EAAE,OAAO,IAAI;AAAA,cAC3C,OAAO;AAEL,qBAAK,uBAAuB,EAAE,MAAM,IAAI;AAAA,cAC1C;AACA,mBAAK,OAAO;AAAA,gBACV,oDAAoD,GAAG,WAAW,GAAG,aAAa,SAAS,QAAQ,CAAC,CAAC,sBAC9E,IAAI,QAAQ,CAAC,CAAC,QAAQ,WAAW,IAAI,UAAU,MAAM;AAAA,cAC9E;AAAA,YACF;AAAA,UACF;AAAA,QACF,QAAQ;AAAA,QAER;AAAA,MACF,OAAO;AACL,aAAK,uBAAuB;AAAA,MAC9B;AAEA,YAAM,uBAAuB,KAAK,iBAAiB,cAAc,SAAS,SAAU,KAAK,iBAAiB,cAAc,SAAS,SAAS;AAC1I,YAAM,sBAAsB,KAAK,gBAAgB,cAAc,SAAS,SAAU,KAAK,gBAAgB,cAAc,SAAS,SAAS;AAGvI,YAAM,KAAK,uBAAuB,WAAW,YAAY,UAAU,WAAW,UAAU,sBAAsB,qBAAqB,KAAK,oBAAoB;AAE5J,WAAK,OAAO,MAAM,4CAA4C;AAAA,IAChE,SAAS,OAAO;AACd,WAAK,SAAS;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,mCACZ,WACA,YACA,UACA,WACA,UACA,cACA,aACA,eACe;AAEf,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MAAa;AAAA,MACb;AAAA,MAAW;AAAA;AAAA,MAGX;AAAA,MAAmB;AAAA,MACnB;AAAA,MAAM;AAAA;AAAA,MAGN;AAAA,MAAmB;AAAA,MACnB;AAAA,MAAM;AAAA;AAAA,MAGN;AAAA,MACA,cAAc,SAAS,IAAI,UAAU,qBAAqB,QAAQ,IAAI,SAAS,4BAA4B,SAAS,CAAC,IAAI,SAAS,CAAC;AAAA,MACnI;AAAA,MAAQ;AAAA;AAAA,MAGR;AAAA,MACA;AAAA,MAAQ;AAAA,MACR;AAAA,MAAM;AAAA,MACN;AAAA,MAAe;AAAA,MACf;AAAA,MAAiB;AAAA,MACjB;AAAA,MAAgB;AAAA,MAChB;AAAA,MAAW;AAAA,MACX;AAAA,MAAS;AAAA,MACT;AAAA,MAAQ;AAAA,MACR;AAAA,MAAM;AAAA,MACN;AAAA,IACF;AAEA,SAAK,OAAO,MAAM,oDAAoD,WAAW,KAAK,GAAG,CAAC,EAAE;AAE5F,SAAK,gBAAgBC,OAAM,UAAU,YAAY;AAAA,MAC/C,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,UAAU;AACxC,WAAK,OAAO,QAAQ,mCAAmC,KAAK;AAC5D,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,SAAS;AACvC,UAAI,SAAS,KAAK,SAAS,MAAM;AAC/B,aAAK,OAAO,OAAO,6CAA6C,IAAI,EAAE;AAAA,MACxE;AACA,WAAK,KAAK,OAAO;AAAA,IACnB,CAAC;AAED,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,WAAK,mBAAmB,IAAI;AAAA,IAC9B,CAAC;AAED,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,YAAM,SAAS,KAAK,SAAS;AAC7B,UAAI,OAAO,SAAS,OAAO,KAAK,OAAO,SAAS,OAAO,GAAG;AACxD,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,KAAK,0BAA0B,KAAM;AAC7C,eAAK,0BAA0B;AAC/B,eAAK,uBAAuB;AAAA,QAC9B;AACA,YAAI,KAAK,yBAAyB,GAAG;AACnC,eAAK,OAAO,QAAQ,oCAAoC,MAAM;AAAA,QAChE;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,uBACZ,WACA,YACA,UACA,WACA,UACA,oBACA,mBACA,gBACe;AACf,UAAM,WAAW,KAAK,QAAQ,YAAY,KAAK,QAAQ;AACvD,UAAM,UAAU,KAAK,QAAQ,WAAW,KAAK,QAAQ;AAIrD,UAAM,gBAAgB,MAAM,SAAS,kBAAkB,KAAK,QAAQ,YAAY;AAChF,UAAM,eAAe,MAAM,QAAQ,kBAAkB,KAAK,QAAQ,WAAW;AAC7E,UAAM,eAAe,KAAK,yBAAyB,KAAK,QAAQ;AAChE,UAAM,cAAc,KAAK,wBAAwB,KAAK,QAAQ;AAC9D,UAAM,kBAAkB,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,YAAY;AACpF,UAAM,iBAAiB,aAAa,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,WAAW;AAIjF,UAAM,mBAAmB,KAAK,QAAQ,qBAAqB;AAC3D,UAAM,aAAa,mBACf,SACC,uBAAuB,iBAAiB,cAAc,YAAY,EAAE,SAAS,KAAK,IAAI,SAAS;AACpG,UAAM,YAAY,mBACd,SACC,sBAAsB,gBAAgB,cAAc,YAAY,EAAE,SAAS,KAAK,IAAI,SAAS;AAGlG,SAAK,OAAO;AAAA,MACV,4CAA4C,UAAU,oBAAoB,iBAAiB,gBAAgB,SAAS,WAAW,SAAS,oBAAoB,gBAAgB,gBAAgB,SAAS;AAAA,IACvM;AAEA,QAAI,KAAK,QAAQ,kBAAkB;AAEjC,WAAK,OAAO;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAQA,UAAM,iBAA2B;AAAA,MAC/B,GAAI,gBAAgB,QAAQ,CAAC,cAAc,OAAO,eAAe,KAAK,CAAC,IAAI,CAAC;AAAA,MAC5E;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,gBAA0B;AAAA,MAC9B,GAAI,gBAAgB,OAAO,CAAC,cAAc,OAAO,eAAe,IAAI,CAAC,IAAI,CAAC;AAAA,MAC1E;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MAAa;AAAA,MACb;AAAA,MAAW;AAAA,MACX;AAAA,MAAc;AAAA;AAAA,MACd;AAAA,MAAoB;AAAA;AAAA;AAAA,MAEpB,GAAG;AAAA;AAAA,MAEH,GAAG;AAAA;AAAA,MAEH;AAAA,MACA,cAAc,SAAS,IAAI,UAAU,qBAAqB,QAAQ,IAAI,SAAS,4BAA4B,SAAS,CAAC,IAAI,SAAS,CAAC;AAAA,MACnI;AAAA,MAAQ;AAAA,MACR;AAAA,MAAQ;AAAA;AAAA;AAAA;AAAA,MAGR;AAAA,MAAM;AAAA,MACN;AAAA,MAAe;AAAA,MACf;AAAA,MAAiB;AAAA,MACjB;AAAA,MAAgB;AAAA,MAChB;AAAA,MAAW;AAAA,MACX;AAAA,MAAS;AAAA,MACT;AAAA,MAAQ;AAAA,MACR;AAAA,MAAM;AAAA,MACN;AAAA;AAAA,IACF;AAEA,SAAK,OAAO;AAAA,MACV,sCAAsC,WAAW,KAAK,GAAG,CAAC;AAAA,IAC5D;AAIA,SAAK,gBAAgBA,OAAM,UAAU,YAAY;AAAA,MAC/C,OAAO,CAAC,QAAQ,QAAQ,QAAQ,MAAM;AAAA,IACxC,CAAC;AAGD,SAAK,cAAc,GAAG,SAAS,CAAC,UAAU;AACxC,WAAK,OAAO,QAAQ,mCAAmC,KAAK;AAC5D,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,SAAS;AACvC,UAAI,SAAS,KAAK,SAAS,MAAM;AAC/B,aAAK,OAAO,OAAO,6CAA6C,IAAI,EAAE;AAAA,MACxE;AACA,WAAK,KAAK,OAAO;AAAA,IACnB,CAAC;AAID,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,WAAK,mBAAmB,IAAI;AAAA,IAC9B,CAAC;AAGD,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,YAAM,SAAS,KAAK,SAAS;AAC7B,UAAI,OAAO,SAAS,OAAO,KAAK,OAAO,SAAS,OAAO,GAAG;AAExD,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,KAAK,0BAA0B,KAAM;AAC7C,eAAK,0BAA0B;AAC/B,eAAK,uBAAuB;AAAA,QAC9B;AACA,YAAI,KAAK,yBAAyB,GAAG;AACnC,eAAK,OAAO,QAAQ,oCAAoC,MAAM;AAAA,QAChE;AAAA,MACF;AAAA,IACF,CAAC;AAGD,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEQ,mBAAmB,MAAc;AACvC,QAAI,CAAC,MAAM,OAAQ;AAGnB,SAAK,eAAe,KAAK,aAAa,SAClC,OAAO,OAAO,CAAC,KAAK,cAAc,IAAI,GAAG,KAAK,aAAa,SAAS,KAAK,MAAM,IAC/E;AAGJ,UAAM,UAAU,IAAI,OAAO;AAC3B,QAAI,KAAK,aAAa,SAAS,SAAS;AAEtC,WAAK,OAAO,OAAO,0DAA0D,KAAK,aAAa,MAAM,oBAAoB;AACzH,WAAK,eAAe,KAAK,aAAa,SAAS,KAAK,aAAa,SAAS,OAAO,IAAI;AAAA,IACvF;AAKA,UAAM,cAAc,KAAK,8BAA8B;AACvD,QAAI,cAAc,GAAG;AACnB,WAAK,eAAe,KAAK,aAAa,SAAS,WAAW;AAAA,IAC5D;AAAA,EACF;AAAA,EAEQ,gCAAwC;AAC9C,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,KAAK,OAAQ,QAAO;AAEzB,UAAM,MAAM,IAAI;AAChB,QAAI,MAAM,EAAG,QAAO;AAEpB,UAAM,iBAAiB,CAAC,MAAsB;AAC5C,UAAI,IAAI,KAAK,OAAO,IAAI,CAAC,MAAM,KAAQ,IAAI,IAAI,CAAC,MAAM,GAAM;AAC1D,YAAI,IAAI,IAAI,CAAC,MAAM,EAAM,QAAO;AAChC,YAAI,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,MAAM,KAAQ,IAAI,IAAI,CAAC,MAAM,EAAM,QAAO;AAAA,MACzE;AACA,aAAO;AAAA,IACT;AAGA,QAAI,UAAU;AACd,aAAS,IAAI,GAAG,IAAI,KAAK,IAAI,MAAM,GAAG,EAAE,GAAG,KAAK;AAC9C,UAAI,eAAe,CAAC,GAAG;AACrB,kBAAU;AACV;AAAA,MACF;AAAA,IACF;AACA,QAAI,UAAU,EAAG,QAAO;AACxB,QAAI,UAAU,GAAG;AAEf,WAAK,eAAe,IAAI,SAAS,OAAO;AACxC,aAAO;AAAA,IACT;AAGA,UAAM,aAAuB,CAAC;AAC9B,aAAS,IAAI,GAAG,IAAI,MAAM,GAAG,KAAK;AAChC,UAAI,eAAe,CAAC,EAAG,YAAW,KAAK,CAAC;AAAA,IAC1C;AACA,QAAI,WAAW,SAAS,EAAG,QAAO;AAElC,UAAM,4BAA4B,CAAC,SAAyB;AAE1D,YAAM,MAAgB,CAAC;AACvB,eAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,cAAM,IAAI,KAAK,CAAC;AAChB,YAAI,KAAK,KAAK,KAAK,IAAI,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,KAAQ,MAAM,GAAM;AACxE;AAAA,QACF;AACA,YAAI,KAAK,CAAC;AAAA,MACZ;AACA,aAAO,OAAO,KAAK,GAAG;AAAA,IACxB;AAEA,UAAM,SAAS,CAAC,MAAc,cAAmE;AAC/F,YAAM,YAAY,KAAK,SAAS;AAChC,UAAI,IAAI;AAER,UAAI,QAAQ;AACZ,aAAO,IAAI,WAAW;AACpB,cAAM,OAAO,KAAK,KAAK,CAAC;AACxB,cAAM,MAAO,QAAS,KAAK,IAAI,KAAO;AACtC,YAAI,QAAQ,GAAG;AACb;AACA;AACA;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI,KAAK,UAAW;AAEpB;AACA,UAAI,UAAU,EAAG,QAAO,EAAE,OAAO,GAAG,MAAM,EAAE;AAC5C,UAAI,IAAI,QAAQ,UAAW;AAC3B,UAAI,OAAO;AACX,eAAS,IAAI,GAAG,IAAI,OAAO,KAAK,KAAK;AACnC,cAAM,OAAO,KAAK,KAAK,CAAC;AACxB,cAAM,MAAO,QAAS,KAAK,IAAI,KAAO;AACtC,eAAQ,QAAQ,IAAK;AAAA,MACvB;AACA,YAAM,SAAS,KAAK,SAAS,IAAI;AACjC,aAAO,EAAE,OAAO,MAAM,EAAE;AAAA,IAC1B;AAEA,UAAM,wBAAwB,CAAC,QAAyB;AACtD,UAAI,IAAI,SAAS,EAAG,QAAO;AAC3B,YAAM,UAAU,IAAI,CAAC,IAAK;AAC1B,UAAI,YAAY,KAAK,YAAY,EAAG,QAAO;AAE3C,YAAM,OAAO,0BAA0B,IAAI,SAAS,CAAC,CAAC;AACtD,YAAM,KAAK,OAAO,MAAM,CAAC;AACzB,UAAI,CAAC,GAAI,QAAO;AAChB,aAAO,GAAG,UAAU;AAAA,IACtB;AAEA,QAAI,iBAAiB;AACrB,QAAI,SAAS;AACb,QAAI,iBAAiB;AAGrB,aAAS,MAAM,GAAG,MAAM,WAAW,SAAS,GAAG,OAAO;AACpD,YAAM,QAAQ,WAAW,GAAG;AAC5B,YAAM,QAAQ,eAAe,KAAK;AAClC,UAAI,CAAC,MAAO;AACZ,YAAM,WAAW,QAAQ;AACzB,YAAM,SAAS,WAAW,MAAM,CAAC;AACjC,UAAI,YAAY,UAAU,YAAY,IAAK;AAC3C,YAAM,MAAM,IAAI,SAAS,UAAU,MAAM;AACzC,UAAI,CAAC,IAAI,OAAQ;AAEjB,YAAM,UAAU,IAAI,CAAC,IAAK;AAC1B,YAAM,QAAQ,YAAY;AAC1B,YAAM,QAAQ,YAAY,KAAK,YAAY;AAE3C,UAAI,OAAO;AAET,YAAI,UAAU,QAAQ,gBAAgB;AACpC,gBAAM,KAAK,IAAI,SAAS,gBAAgB,KAAK;AAC7C,cAAI,GAAG,OAAQ,MAAK,KAAK,cAAc,EAAE;AACzC,2BAAiB;AAAA,QACnB;AACA,yBAAiB;AACjB,iBAAS;AACT;AAAA,MACF;AAEA,UAAI,OAAO;AACT,cAAM,aAAa,sBAAsB,GAAG;AAC5C,YAAI,YAAY;AACd,cAAI,UAAU,QAAQ,gBAAgB;AACpC,kBAAM,KAAK,IAAI,SAAS,gBAAgB,KAAK;AAC7C,gBAAI,GAAG,OAAQ,MAAK,KAAK,cAAc,EAAE;AACzC,6BAAiB;AACjB,6BAAiB;AAAA,UACnB;AACA,mBAAS;AAAA,QACX,OAAO;AACL,mBAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAGA,WAAO,KAAK,IAAI,GAAG,cAAc;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,qBAAoC;AAChD,QAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,eAAe,CAAC,KAAK,YAAY;AAChE;AAAA,IACF;AAEA,UAAM,2BAA2B,KAAK,QAAQ,mBAAmB,QAAS,KAAK,QAAQ,4BAA4B;AAEnH,UAAM,aAAa,KAAK,cAAc,MAAM,CAAC;AAC7C,UAAM,YAAY,KAAK,cAAc,MAAM,CAAC;AAE5C,QAAI,CAAC,cAAc,CAAC,WAAW;AAC7B,WAAK,OAAO,QAAQ,8CAA8C;AAClE;AAAA,IACF;AAGA,UAAM,YAAY,YAAY;AAC5B,UAAI;AACF,YAAI,cAAc,CAAC;AACnB,YAAI,KAAK,iBAAiB,MAAM;AAC9B,cAAI;AACF,gBAAI,CAAC,4BAA4B,KAAK,gBAAgB,YAAY;AAChE,oBAAM,UAAU,WAAW,MAAM,KAAK,SAAS,KAAK,gBAAgB,IAAI,CAAC;AACzE,4BAAc,eAAe,QAAQ,KAAK,gBAAgB,UAAU;AACpE,kBAAI,CAAC,SAAS;AACZ,sBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,6BAAW,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,gBAC1C,CAAC;AAAA,cACH;AAAA,YACF;AACA,iBAAK,kBAAkB;AAAA,UACzB,QAAQ;AAAA,UAER;AAAA,QACF;AACA,yBAAiB,SAAS,KAAK,aAAc;AAC3C,cAAI,CAAC,KAAK,OAAQ;AAClB,cAAI,MAAM,OAAO;AAEf,gBAAI,CAAC,KAAK,YAAa,MAAK,cAAc;AAC1C,gBAAI,KAAK,gBAAgB,QAAS,MAAK,KAAK,cAAc,MAAM,IAAI;AACpE;AAAA,UACF;AAEA,cAAI,CAAC,aAAa;AAChB,gBAAI,CAAC,MAAM,WAAY;AACvB,0BAAc;AAAA,UAChB;AAEA,cAAI;AACF,kBAAM,UAAU,WAAW,MAAM,KAAK,SAAS,MAAM,IAAI,CAAC;AAC1D,gBAAI,CAAC,SAAS;AACZ,oBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,2BAAW,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,cAC1C,CAAC;AAAA,YACH;AAAA,UACF,SAAS,OAAO;AACd,kBAAM,OAAQ,OAAe;AAC7B,gBAAI,SAAS,WAAW,SAAS,8BAA8B;AAC7D,mBAAK,OAAO,MAAM,6CAA6C;AAC/D;AAAA,YACF;AACA,iBAAK,OAAO,QAAQ,gDAAgD,KAAK;AAAA,UAC3E;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,YAAI,KAAK,QAAQ;AACf,eAAK,OAAO,QAAQ,4CAA4C,KAAK;AAAA,QACvE;AAAA,MACF,UAAE;AACA,YAAI;AACF,qBAAW,IAAI;AAAA,QACjB,QAAQ;AAAA,QAAC;AAAA,MACX;AAAA,IACF;AAGA,UAAM,WAAW,YAAY;AAC3B,UAAI;AACF,YAAI,aAAa,CAAC;AAClB,YAAI,KAAK,gBAAgB,MAAM;AAC7B,cAAI;AACF,gBAAI,CAAC,4BAA4B,KAAK,eAAe,YAAY;AAC/D,oBAAM,UAAU,UAAU,MAAM,KAAK,SAAS,KAAK,eAAe,IAAI,CAAC;AACvE,2BAAa,cAAc,QAAQ,KAAK,eAAe,UAAU;AACjE,kBAAI,CAAC,SAAS;AACZ,sBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,4BAAU,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,gBACzC,CAAC;AAAA,cACH;AAAA,YACF;AACA,iBAAK,iBAAiB;AAAA,UACxB,QAAQ;AAAA,UAER;AAAA,QACF;AACA,yBAAiB,SAAS,KAAK,YAAa;AAC1C,cAAI,CAAC,KAAK,OAAQ;AAClB,cAAI,MAAM,OAAO;AAEf,gBAAI,CAAC,KAAK,YAAa,MAAK,cAAc;AAC1C,gBAAI,KAAK,gBAAgB,OAAQ,MAAK,KAAK,cAAc,MAAM,IAAI;AACnE;AAAA,UACF;AAEA,cAAI,CAAC,YAAY;AACf,gBAAI,CAAC,MAAM,WAAY;AACvB,yBAAa;AAAA,UACf;AAEA,cAAI;AACF,kBAAM,UAAU,UAAU,MAAM,KAAK,SAAS,MAAM,IAAI,CAAC;AACzD,gBAAI,CAAC,SAAS;AACZ,oBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,0BAAU,KAAK,SAAS,MAAM,QAAQ,CAAC;AAAA,cACzC,CAAC;AAAA,YACH;AAAA,UACF,SAAS,OAAO;AACd,kBAAM,OAAQ,OAAe;AAC7B,gBAAI,SAAS,WAAW,SAAS,8BAA8B;AAC7D,mBAAK,OAAO,MAAM,4CAA4C;AAC9D;AAAA,YACF;AACA,iBAAK,OAAO,QAAQ,+CAA+C,KAAK;AAAA,UAC1E;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,YAAI,KAAK,QAAQ;AACf,eAAK,OAAO,QAAQ,2CAA2C,KAAK;AAAA,QACtE;AAAA,MACF,UAAE;AACA,YAAI;AACF,oBAAU,IAAI;AAAA,QAChB,QAAQ;AAAA,QAAC;AAAA,MACX;AAAA,IACF;AAGA,YAAQ,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,UAAU;AACtD,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,QAAQ,gDAAgD,KAAK;AACzE,aAAK,KAAK,SAAS,KAAK;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,SAAS,YAA4B;AAC3C,QAAI,CAAC,YAAY,OAAQ,QAAO;AAGhC,QAAI,WAAW,UAAU,KAAK,WAAW,CAAC,MAAM,KAAQ,WAAW,CAAC,MAAM,GAAM;AAC9E,UAAI,WAAW,CAAC,MAAM,EAAM,QAAO;AACnC,UAAI,WAAW,UAAU,KAAK,WAAW,CAAC,MAAM,KAAQ,WAAW,CAAC,MAAM,EAAM,QAAO;AAAA,IACzF;AAIA,QAAI;AACF,UAAI,MAAM;AACV,YAAM,QAAkB,CAAC;AACzB,aAAO,MAAM,KAAK,WAAW,QAAQ;AACnC,cAAM,IAAI,WAAW,aAAa,GAAG;AACrC,eAAO;AACP,YAAI,CAAC,KAAK,MAAM,IAAI,WAAW,QAAQ;AACrC,iBAAO;AAAA,QACT;AACA,cAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,cAAM,KAAK,WAAW,SAAS,KAAK,MAAM,CAAC,CAAC;AAC5C,eAAO;AAAA,MACT;AACA,UAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,aAAO,OAAO,OAAO,KAAK;AAAA,IAC5B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AAEA,SAAK,SAAS;AACd,SAAK,OAAO,MAAM,gDAAgD;AAGlE,QAAI,KAAK,eAAe;AACtB,UAAI;AACF,aAAK,cAAc,OAAO,IAAI;AAC9B,aAAK,cAAc,KAAK,SAAS;AACjC,mBAAW,MAAM;AACf,cAAI;AACF,iBAAK,eAAe,KAAK,SAAS;AAAA,UACpC,QAAQ;AAAA,UAAC;AAAA,QACX,GAAG,GAAI;AAAA,MACT,QAAQ;AAAA,MAAC;AACT,WAAK,gBAAgB;AAAA,IACvB;AAEA,QAAI,KAAK,cAAc,UAAU;AAG/B,YAAM,QAAQ,IAAI;AAAA,QAChB,KAAK,eAAe,KAAK,WAAW;AAAA,QACpC,KAAK,eAAe,KAAK,UAAU;AAAA,MACrC,CAAC;AACD,WAAK,cAAc;AACnB,WAAK,aAAa;AAAA,IACpB,OAAO;AACL,WAAK,cAAc;AACnB,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,OAAO,MAAM,4CAA4C;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AACF;;;AD5gCA,eAAsB,uBACpB,SAC2B;AAC3B,QAAM,kBACJ,OAAO,YAAY,eAAe,OAAO,QAAQ,QAAQ,WACrD,QAAQ,MACR;AACN,QAAM,oBAAoB,MAAM;AAC9B,QAAI;AACF,YAAM,IAAI;AACV,YAAM,IAAI;AACV,UAAI,CAAC,EAAE,CAAC,GAAG;AACT,UAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC;AAAA,MAC3C;AACA,aAAO,OAAO,EAAE,CAAC,CAAC;AAAA,IACpB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAEH,QAAM,YAAY,CAAC,YAAoB;AACrC,QAAI;AACF,YAAM,SAAc,QAAQ;AAC5B,YAAM,SAAS,8BAA8B,eAAe,SAAS,gBAAgB;AAErF,UAAI,QAAQ,KAAM,QAAO,KAAK,GAAG,MAAM,IAAI,OAAO,EAAE;AAAA,eAC3C,QAAQ,IAAK,QAAO,IAAI,GAAG,MAAM,IAAI,OAAO,EAAE;AAAA,eAC9C,QAAQ,MAAO,QAAO,MAAM,GAAG,MAAM,IAAI,OAAO,EAAE;AAAA,IAC7D,QAAQ;AAAA,IAER;AAAA,EACF;AAKA,QAAM,mCAAmC,CAAC,OAAmC;AAC3E,QAAI,OAAO,OAAO,YAAY,OAAO,SAAS,EAAE,KAAK,KAAK,EAAG,QAAO;AACpE,WAAO;AAAA,EACT;AASA,QAAM,YAAY,sBAAsB,OAAO;AAC/C,MAAI,CAAC,WAAW;AACd;AAAA,MACE;AAAA,IACF;AACA,WAAO,MAAM,+BAA+B,OAAO;AAAA,EACrD;AAEA;AAAA,IACE,eAAe,sBAAsB,SAAS,CAAC,iBAAiB,QAAQ,eAAe,KAAK;AAAA,EAC9F;AAEA,QAAM,WAAW,qBAAqB,IAAI,SAAS;AACnD,MAAI,YAAY,SAAS,OAAO,OAAO,WAAW;AAChD;AAAA,MACE,uBAAuB,SAAS,OAAO,IAAI,SAAS,SAAS,OAAO,IAAI,aAAa,SAAS,QAAQ;AAAA,IACxG;AACA,WAAO,2BAA2B,WAAW,UAAU,SAAS;AAAA,EAClE;AACA,MAAI,UAAU;AAEZ;AAAA,MACE,0CAA0C,SAAS,OAAO,OAAO,SAAS;AAAA,IAC5E;AACA,yBAAqB,OAAO,SAAS;AAAA,EACvC;AAEA,QAAM,WAAW,qBAAqB,IAAI,SAAS;AACnD,MAAI,UAAU;AACZ,cAAU,2BAA2B;AACrC,UAAM;AACN,UAAMC,WAAU,qBAAqB,IAAI,SAAS;AAClD,QAAIA,UAAS;AACX;AAAA,QACE,yCAAyCA,SAAQ,OAAO,IAAI,SAASA,SAAQ,OAAO,IAAI,aAAaA,SAAQ,QAAQ;AAAA,MACvH;AACA,aAAO,2BAA2B,WAAWA,UAAS,SAAS;AAAA,IACjE;AAEA;AAAA,MACE;AAAA,IACF;AACA,WAAO,MAAM,+BAA+B,OAAO;AAAA,EACrD;AAEA,QAAM,iBAAiB,YAAY;AACjC,UAAM,iBAAiB;AAAA,MACrB,QAAQ;AAAA,IACV;AACA;AAAA,MACE,sDAAsD,cAAc;AAAA,IACtE;AACA,UAAM,SAAS,MAAM,+BAA+B;AAAA,MAClD,GAAG;AAAA;AAAA,MAEH;AAAA,IACF,CAAC;AAED,UAAM,QAA4B;AAAA,MAChC;AAAA,MACA,UAAU;AAAA,IACZ;AACA,yBAAqB,IAAI,WAAW,KAAK;AACzC,cAAU,gBAAgB,OAAO,IAAI,SAAS,OAAO,IAAI,EAAE;AAG3D,WAAO,OAAO,KAAK,SAAS,MAAM;AAChC,YAAM,UAAU,qBAAqB,IAAI,SAAS;AAClD,UAAI,SAAS,WAAW,QAAQ;AAC9B,kBAAU,uDAAuD;AACjE,6BAAqB,OAAO,SAAS;AAAA,MACvC;AAAA,IACF,CAAC;AAAA,EACH,GAAG,EAAE,QAAQ,MAAM;AACjB,yBAAqB,OAAO,SAAS;AAAA,EACvC,CAAC;AAED,uBAAqB,IAAI,WAAW,aAAa;AACjD,QAAM;AAEN,QAAM,UAAU,qBAAqB,IAAI,SAAS;AAClD,MAAI,QAAS,QAAO,2BAA2B,WAAW,SAAS,SAAS;AAC5E,SAAO,MAAM,+BAA+B,OAAO;AACrD;AAOA,IAAM,uBAAuB,oBAAI,IAAgC;AACjE,IAAM,uBAAuB,oBAAI,IAA2B;AAE5D,IAAM,wBAAwB,CAC5B,YACuB;AAEvB,MAAI,CAAC,QAAQ,SAAU;AAIvB,QAAM,aACJ,QAAQ,YAAY,SAAY,cAAc,OAAO,QAAQ,OAAO;AACtE,QAAM,aAAa,QAAQ,WAAW;AAMtC,QAAM,cAAc,QAAQ,QAAQ,WAAW;AAG/C,QAAM,mBAAmB,QAAQ,oBAAoB;AACrD,QAAM,mBAAmB,QAAQ,oBAAoB;AAErD,SAAO;AAAA,IACL;AAAA,IACA,UAAU,QAAQ,QAAQ;AAAA,IAC1B,MAAM,UAAU;AAAA,IAChB,WAAW,QAAQ,OAAO;AAAA,IAC1B,WAAW,UAAU;AAAA,IACrB,QAAQ,cAAc,MAAM,GAAG;AAAA,IAC/B,OAAO,gBAAgB;AAAA,IACvB,OAAO,gBAAgB;AAAA,EACzB,EAAE,KAAK,GAAG;AACZ;AAEA,IAAM,wBAAwB,CAAC,QAAwB;AAGrD,SAAO,IAAI,QAAQ,iBAAiB,kBAAkB;AACxD;AAEA,IAAM,6BAA6B,CACjC,KACA,OACA,cACqB;AACrB,QAAM;AACN,cAAY,sBAAsB,GAAG,aAAa,MAAM,QAAQ,EAAE;AAElE,MAAI,WAAW;AACf,QAAM,YAAY,MAAM;AAExB,QAAM,QAAQ,OAAO,WAAoC;AACvD,QAAI,SAAU;AACd,eAAW;AAEX,UAAM,UAAU,qBAAqB,IAAI,GAAG;AAE5C,QAAI,CAAC,WAAW,QAAQ,WAAW,UAAW;AAE9C,YAAQ,WAAW,KAAK,IAAI,GAAG,QAAQ,WAAW,CAAC;AACnD;AAAA,MACE,sBAAsB,GAAG,aAAa,QAAQ,QAAQ,WAAW;AAAA,QAC9D,QAAgB,WAAW,UAAU;AAAA,MACxC,CAAC;AAAA,IACH;AACA,QAAI,QAAQ,WAAW,EAAG;AAE1B,yBAAqB,OAAO,GAAG;AAC/B,gBAAY,+CAA+C;AAC3D,UAAM,UAAU,MAAM,UAAU,wBAAwB;AAAA,EAC1D;AAGA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,EACF;AACF;AAEA,eAAe,+BACb,SAC2B;AAC3B,QAAM,cAAc,QAAQ,YAAY;AAExC,QAAM,gCAAgC,CACpCC,iBAOe;AACf,QAAI,CAACA,aAAa;AAClB,UAAM,KAAK,OAAOA,YAAW;AAE7B,UAAM,YAAY,CAAC,MACjB,MAAM,UAAU,MAAM,SAAS,MAAM,QAChC,IACD;AAON,QACE,GAAG,WAAW,mBAAmB,KACjC,GAAG,WAAW,iBAAiB,GAC/B;AACA,YAAM,SAA4B,GAAG,WAAW,iBAAiB,IAC7D,SACA;AACJ,YAAM,QAAQ,GAAG,MAAM,GAAG,EAAE,OAAO,OAAO;AAG1C,UAAI,MAAM,UAAU,GAAG;AACrB,cAAM,QAAQ,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC;AACpD,cAAM,OAAO,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC;AACnD,cAAM,eAAe,UAAU,KAAK;AACpC,cAAM,cAAc,UAAU,IAAI;AAClC,eAAO;AAAA,UACL;AAAA,UACA,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,UACvC,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,QACvC;AAAA,MACF;AACA,aAAO,EAAE,OAAO;AAAA,IAClB;AAEA;AAAA,EACF;AAEA,QAAM,gBAA0C;AAAA,IAC9C,SAAS,QAAQ;AAAA,IACjB,WAAW;AAAA,IACX,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,IACpE,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,EACtE;AAEA,QAAM,UAAU,QAAQ,OAAQ,MAAM,QAAQ,SAAS,aAAa;AACpE,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AAEA,QAAM,wBACJ,QAAQ,iBAAkB,MAAM,QAAQ,mBAAmB;AAE7D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,qBAAqB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,MAAI;AAOJ,QAAM,cAAc,oBAAI,IAAwB;AAChD,cAAY,IAAI,OAAO;AACvB,MAAI,uBAAuB;AACzB,gBAAY,IAAI,sBAAsB,QAAQ;AAChD,MAAI,uBAAuB;AACzB,gBAAY,IAAI,sBAAsB,OAAO;AAI/C,QAAM,kBAAkB,uBAAuB,cAAc,MAAS;AACtE,QAAM,gBACJ,WAAW,YAAY,YAAY,YAAY,OAAO,KAAK;AAC7D,QAAM,YAAY,cACd,qCAAqC,OAAO,GAAG,aAAa,GAAG,cAAc,OAAO,WAAW,KAAK,EAAE,MACtG,sBAAsB,OAAO,YAAY,OAAO,GAAG,aAAa;AACpE,QAAM,MAAM,CAAC,YAAoB;AAC/B,QAAI;AACF,UAAI,QAAQ,MAAM;AAChB,eAAO,KAAK,GAAG,SAAS,IAAI,OAAO,EAAE;AAAA,MACvC,WAAW,QAAQ,KAAK;AACtB,eAAO,IAAI,GAAG,SAAS,IAAI,OAAO,EAAE;AAAA,MACtC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,qBAAqB,CAAC,WAAmB;AAC7C,UAAM,UAAU,QAAQ,4BAA4B;AACpD;AAAA,MACE,GAAG,MAAM,eAAe,QAAQ,KAAK,UAAU,QAAQ,QAAQ,IAAI,KAAK,QAAQ,KAAK,KAAK,IAAI,CAAC,MAAM,EAAE;AAAA,IACzG;AAAA,EACF;AAEA;AAAA,IACE,kBAAkB,IAAI,YAAY,gBAAgB,YAAY,gBAAgB,sBAAsB,qBAAqB,KAAK,sBAAsB,iBAAiB,oBAAoB,eAAe,mBAAmB,cAAc,cAAc,WAAW;AAAA,EACpQ;AAEA,MAAI;AACJ,MAAI,oBAAoB;AAExB,MAAI,aAAa;AAEf,UAAM,eAAe,kBAAkB,gBAAgB;AACvD,UAAM,cAAc,kBAAkB,eAAe;AACrD,UAAM,YAAY,8BAA8B,WAAW;AAG3D,UAAM,gBAA+B;AACrC,QAAI,mBACF,WAAW,eAAe;AAM5B,QAAI,kBAAkB;AACtB,QAAI;AACF,YAAM,mBAAmB,uBAAuB,YAAY;AAC5D,YAAM,WACJ,MAAM,iBAAiB,kBAAkB,YAAY;AACvD,YAAM,UAAiB,MAAM,QAAQ,QAAQ,IACzC,WACA,MAAM,QAAQ,UAAU,OAAO,IAC7B,SAAS,UACT,CAAC;AACP,YAAM,OAAO,QAAQ,KAAK,CAAC,MAAW,GAAG,YAAY,MAAM;AAC3D,YAAM,MACJ,OAAO,MAAM,iBAAiB,WAC1B,KAAK,aAAa,YAAY,IAC9B;AACN,wBAAkB,IAAI,SAAS,KAAK;AAAA,IACtC,QAAQ;AAAA,IAER;AAEA,QAAI,oBACF,WAAW,iBACV,kBAAkB,QACf,QACA,kBAAkB,UAAU,kBAC1B,SACA;AAER,QAAI,WAAW,eAAe,UAAU,gBAAgB,eAAe;AACrE;AAAA,QACE,uDAAuD,UAAU,WAAW,WAAW,aAAa;AAAA,MACtG;AAAA,IACF;AACA;AAAA,MACE,4CAA4C,aAAa,aAC3C,YAAY,aAAa,iBAAiB,cAAc,WAAW,aAAa,gBAAgB,YAClG,WAAW,UAAU,QAAQ,oBAAoB,eAAe;AAAA,IAC9E;AAEA,UAAM,WAAW,uBAAuB,YAAY;AACpD,UAAM,UAAU,uBAAuB,WAAW;AAIlD,UAAM,YAAY,kBAAkB;AACpC,UAAM,mBAAmB,kBAAkB;AAG3C,QAAI;AACJ,QAAI;AACJ,QAAI,WAAW,WAAW,QAAQ;AAChC,YAAM,QAAQ,QAAQ,kBAAkB,KAAK;AAE7C,YAAM,qBAAqB,OAAO,WAKoC;AACpE,cAAM,EAAE,YAAY,IAAI,MAAM,QAAQ,wBAAwB;AAAA,UAC5D,SAAS,OAAO;AAAA,UAChB;AAAA,UACA,eAAe;AAAA,UACf,MAAM,OAAO;AAAA,QACf,CAAC;AAED,cAAM,aAAa,YAAY;AAAA,UAC7B,CAAC,MACC,EAAE,cAAc,UAChB,EAAE,SAAS,OAAO,eAClB,QAAQ,EAAE,WAAW;AAAA,QACzB;AAEA,cAAM,QAAQ,WAAW,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,OAAO;AACjE,YAAI,OAAO,aAAa;AACtB,iBAAO;AAAA,YACL,aAAa,MAAM;AAAA,YACnB,eAAe,MAAM;AAAA,UACvB;AAAA,QACF;AAIA,YAAI,OAAO,YAAY,OAAO;AAC5B,gBAAM,eAAe,WAAW,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM;AAChE,cAAI,cAAc,aAAa;AAC7B,mBAAO;AAAA,cACL,aAAa,aAAa;AAAA,cAC1B,eAAe,aAAa;AAAA,YAC9B;AAAA,UACF;AAAA,QACF;AAEA,cAAM,YAAY,YACf;AAAA,UACC,CAAC,MAAM,EAAE,cAAc,UAAU,EAAE,SAAS,OAAO;AAAA,QACrD,EACC,IAAI,CAAC,MAAM,GAAG,EAAE,OAAO,IAAI,EAAE,EAAE,EAAE,EACjC,KAAK,IAAI;AAEZ,cAAM,IAAI;AAAA,UACR,gDAAgD,OAAO,WAAW,6BAA6B,OAAO,OAAO,YAAY,OAAO,OAAO,WAAW,KAAK,kBACtI,aAAa,MAAM;AAAA,QAEtC;AAAA,MACF;AAGA,YAAM,gBAAgB,MAAM,mBAAmB;AAAA,QAC7C,SAAS;AAAA,QACT,MAAM;AAAA,QACN,aAAa;AAAA,QACb,SAAS;AAAA,MACX,CAAC;AAED,qBAAe,cAAc;AAC7B,0BAAoB,cAAc;AAGlC,YAAM,eAAe,MAAM,mBAAmB;AAAA,QAC5C,SAAS;AAAA,QACT,MAAM;AAAA,QACN,aAAa;AAAA,QACb,SAAS;AAAA,MACX,CAAC;AAED,oBAAc,aAAa;AAC3B,UAAI,aAAa,kBAAkB,kBAAkB;AACnD;AAAA,UACE,iDAAiD,gBAAgB,WAAW,aAAa,aAAa;AAAA,QACxG;AAAA,MACF;AACA,yBAAmB,aAAa;AAAA,IAClC;AAEA,kBAAc,IAAI,gBAAgB;AAAA,MAChC,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd,aAAa;AAAA,MACb,GAAI,gBAAgB,cAAc,EAAE,cAAc,YAAY,IAAI,CAAC;AAAA,MACnE,GAAI,kBAAkB,gBAClB,EAAE,eAAe,iBAAiB,cAAc,IAChD,CAAC;AAAA,MACL,aAAa,kBAAkB,eAAe;AAAA,MAC9C,SAAS,kBAAkB,WAAW;AAAA;AAAA,MAEtC,WAAW,kBAAkB,aAAa;AAAA,MAC1C,GAAI,kBAAkB,UAAU,SAC5B,EAAE,OAAO,iBAAiB,MAAM,IAChC,CAAC;AAAA,MACL,GAAI,cAAc,SACd,EAAE,UAAU,IACZ,mBACE,EAAE,WAAW,KAAK,IAClB,CAAC;AAAA,MACP,GAAI,kBAAkB,qBAAqB,SACvC,EAAE,kBAAkB,iBAAiB,iBAAiB,IACtD,CAAC;AAAA,MACL,GAAI,kBAAkB,qBAAqB,SACvC,EAAE,kBAAkB,iBAAiB,iBAAiB,IACtD,CAAC;AAAA,MACL;AAAA,IACF,CAAC;AAED,wBAAoB;AACpB,UAAM,YAAY,MAAM;AACxB;AAAA,MACE;AAAA,IACF;AAAA,EACF,OAAO;AAEL,UAAM,KAAK;AAMX,UAAM,WAAW,QAAQ;AACzB,QAAI;AAEJ,QAAI,UAAU;AACZ,YAAM,aAAa,QAAQ,QAAQ,MAAM,EAAE,IAAI,OAAO,GAAG,WAAW,YAAY,YAAY,IAAI,OAAO,KAAK,EAAE;AAC9G,yBAAmB,MAAM,QAAQ;AAAA,QAC/B;AAAA,QACA;AAAA,MACF;AACA,qBAAe,iBAAiB;AAChC,yBAAmB,8BAA8B,UAAU,EAAE;AAAA,IAC/D,OAAO;AACL,qBAAe,QAAQ;AAAA,IACzB;AAEA,kBAAc,IAAI,oBAAoB;AAAA,MACpC,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,YAAY,MAAM;AACxB;AAAA,MACE,sBAAsB,EAAE,YAAY,OAAO,GAAG,WAAW,cAAc,QAAQ,KAAK,EAAE;AAAA,IACxF;AAAA,EACF;AAEA,QAAM,kBAAkB,YAMnB;AACH,QAAI,mBAAmB;AAErB,aAAO,MAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC5C,cAAM,UAAU,WAAW,MAAM;AAC/B,kBAAQ;AACR;AAAA,YACE,IAAI;AAAA,cACF,4DAA4D,OAAO;AAAA,YACrE;AAAA,UACF;AAAA,QACF,GAAG,iBAAiB;AAEpB,cAAM,UAAU,CAAC,MAAe;AAC9B,kBAAQ;AACR,iBAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,QACtD;AAEA,cAAM,UAAU,CAAC,UAAkB;AAGjC,gBAAM,YAAuB;AAE7B,cAAI;AACF,kBAAM,EAAE,KAAK,KAAK,eAAe,IAC/B,mCAAmC,KAAK;AAC1C,gBAAI,CAAC,OAAO,CAAC,KAAK;AAEhB;AAAA,YACF;AACA,oBAAQ;AACR,oBAAQ;AAAA,cACN;AAAA,cACA,YAAY;AAAA,cACZ,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;AAAA,cAC3C,MAAM,EAAE,KAAK,IAAI;AAAA,YACnB,CAAC;AAAA,UACH,SAAS,GAAG;AAEV;AAAA,UACF;AAAA,QACF;AAEA,cAAM,UAAU,MAAM;AACpB,kBAAQ;AACR;AAAA,YACE,IAAI;AAAA,cACF,oDAAoD,OAAO;AAAA,YAC7D;AAAA,UACF;AAAA,QACF;AAEA,cAAM,UAAU,MAAM;AACpB,uBAAa,OAAO;AACpB,UAAC,YAAgC;AAAA,YAC/B;AAAA,YACA;AAAA,UACF;AACA,UAAC,YAAgC;AAAA,YAC/B;AAAA,YACA;AAAA,UACF;AACA,UAAC,YAAgC;AAAA,YAC/B;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAEA,QAAC,YAAgC,GAAG,SAAgB,OAAc;AAClE,QAAC,YAAgC;AAAA,UAC/B;AAAA,UACA;AAAA,QACF;AACA,QAAC,YAAgC,GAAG,SAAgB,OAAc;AAAA,MACpE,CAAC;AAAA,IACH,OAAO;AAEL,aAAO,MAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC5C,cAAM,UAAU,WAAW,MAAM;AAC/B,kBAAQ;AACR;AAAA,YACE,IAAI;AAAA,cACF,yDAAyD,OAAO,YAAY,OAAO;AAAA,YACrF;AAAA,UACF;AAAA,QACF,GAAG,iBAAiB;AAEpB,cAAM,UAAU,CAAC,MAAe;AAC9B,kBAAQ;AACR,iBAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,QACtD;AAEA,cAAM,OAAO,CAAC,OAAY;AACxB,cAAI,CAAC,IAAI,WAAY;AACrB,gBAAM,YAAY,GAAG;AACrB,gBAAM,aAAa,GAAG;AAEtB,cAAI,cAAc,QAAQ;AACxB,kBAAM,EAAE,KAAAC,MAAK,KAAAC,MAAK,eAAe,IAC/B,mCAAmC,UAAU;AAC/C,gBAAI,CAACD,QAAO,CAACC,KAAK;AAClB,oBAAQ;AACR,oBAAQ;AAAA,cACN;AAAA,cACA;AAAA,cACA,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;AAAA,cAC3C,MAAM,EAAE,KAAAD,MAAK,KAAAC,KAAI;AAAA,YACnB,CAAC;AACD;AAAA,UACF;AAEA,gBAAM,EAAE,KAAK,KAAK,IAAI,IACpB,mCAAmC,UAAU;AAC/C,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAK;AAC1B,kBAAQ;AACR,kBAAQ,EAAE,WAAW,YAAY,MAAM,EAAE,KAAK,KAAK,IAAI,EAAE,CAAC;AAAA,QAC5D;AAEA,cAAM,UAAU,MAAM;AACpB,uBAAa,OAAO;AACpB,UAAC,YAAoC;AAAA,YACnC;AAAA,YACA;AAAA,UACF;AACA,UAAC,YAAoC;AAAA,YACnC;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAEA,QAAC,YAAoC,GAAG,SAAgB,OAAc;AACtE,QAAC,YAAoC;AAAA,UACnC;AAAA,UACA;AAAA,QACF;AAMA,cAAM,aACJ,YACA,6BAA6B;AAC/B,YAAI,YAAY;AACd,kBAAQ;AACR,iBAAO,UAAU;AAAA,QACnB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,gBAAgB;AAAA,EACnC,SAAS,GAAG;AAGV,QAAI;AACF,YAAM,YAAY,KAAK;AAAA,IACzB,QAAQ;AAAA,IAER;AAGA,QAAI,kBAAkB;AACpB,UAAI;AACF,cAAM,iBAAiB,QAAQ;AAAA,MACjC,QAAQ;AAAA,MAER;AAAA,IACF;AAMA,QAAI,CAAC,kBAAkB;AACrB,UAAI,oBAAoB;AACtB,cAAM,QAAQ;AAAA,UACZ,MAAM,KAAK,WAAW,EAAE,IAAI,OAAO,MAAM;AACvC,gBAAI;AACF,oBAAM,EAAE,MAAM;AAAA,YAChB,QAAQ;AAAA,YAER;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AAIL,cAAM,UAAU,cAAc,MAAQ;AACtC,mBAAW,KAAK,MAAM,KAAK,WAAW,GAAG;AACvC,cAAI;AACF,YAAC,GAAW,QAAQ;AAAA,cAClB;AAAA,cACA;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACA,MAAI,qBAAqB,SAAS,cAAc,mBAAmB;AACjE;AAAA,MACE,wCAAwC,iBAAiB,WAAW,SAAS,SAAS;AAAA,IACxF;AAAA,EACF;AAGA,MAAI,MAAM;AACV,MAAI;AACF,QAAI,aAAa;AAEf,YAAM,eAAe,kBAAkB,gBAAgB;AACvD,YAAM,WAAW,uBAAuB,YAAY;AACpD,YAAM,WAAgB,MAAM,SAAS,kBAAkB,YAAY;AACnE,YAAM,UAAiB,MAAM,QAAQ,QAAQ,IACzC,WACA,MAAM,QAAQ,UAAU,OAAO,IAC7B,SAAS,UACT,CAAC;AAEP,YAAM,YAAY,8BAA8B,WAAW;AAC3D,YAAM,uBACJ,WAAW,eAAe;AAC5B,UAAI,kBAAkB;AACtB,UAAI;AACF,cAAM,OAAO,QAAQ,KAAK,CAAC,MAAW,GAAG,YAAY,MAAM;AAC3D,cAAM,MACJ,OAAO,MAAM,iBAAiB,WAC1B,KAAK,aAAa,YAAY,IAC9B;AACN,0BAAkB,IAAI,SAAS,KAAK;AAAA,MACtC,QAAQ;AAAA,MAER;AACA,YAAM,eACJ,WAAW,iBACV,yBAAyB,QACtB,QACA,yBAAyB,UAAU,kBACjC,SACA;AACR,YAAM,SAAS,QAAQ,KAAK,CAAC,MAAW,GAAG,YAAY,YAAY;AACnE,YAAM,KAAK,OAAO,QAAQ,SAAS;AACnC,UAAI,OAAO,SAAS,EAAE,KAAK,KAAK,EAAG,OAAM;AAAA,IAC3C,OAAO;AACL,YAAM,WAAgB,MAAM,QAAQ,kBAAkB,OAAQ;AAC9D,YAAM,UAAiB,MAAM,QAAQ,QAAQ,IACzC,WACA,MAAM,QAAQ,UAAU,OAAO,IAC7B,SAAS,UACT,CAAC;AACP,YAAM,SAAS,QAAQ,KAAK,CAAC,MAAW,GAAG,YAAY,OAAO;AAC9D,YAAM,KAAK,OAAO,QAAQ,SAAS;AACnC,UAAI,OAAO,SAAS,EAAE,KAAK,KAAK,EAAG,OAAM;AAAA,IAC3C;AAAA,EACF,QAAQ;AAAA,EAER;AAIA,MAAI;AAQJ,QAAM,gBAAgB,YAAmC;AACvD,WAAO,MAAM,IAAI,QAAQ,CAAC,YAAY;AACpC,UAAI,cAAc;AAClB,UAAI,gBAAgB;AACpB,YAAM,sBAAsB,oBACxB,KAAK,IAAI,KAAQ,iBAAiB,IAClC;AACJ,YAAM,UAAU,WAAW,MAAM;AAC/B,gBAAQ;AACR,YAAI,CAAC,aAAa;AAChB,kBAAQ,MAAS;AACjB;AAAA,QACF;AAEA,cAAM,OAAO,gBAAgB,EAAE,YAAY,KAAM,UAAU,EAAE;AAC7D,cAAM,YAAY,+BAA+B;AAAA,UAC/C,YAAY,KAAK;AAAA,UACjB,UAAU,KAAK;AAAA,QACjB,CAAC;AACD,YAAI,CAAC,WAAW;AACd,iBAAO;AAAA,YACL,6EAA6E,KAAK,UAAU,aAAa,KAAK,QAAQ;AAAA,UACxH;AACA,kBAAQ,MAAS;AACjB;AAAA,QACF;AAEA,eAAO;AAAA,UACL,6FAA6F,KAAK,UAAU,aAAa,KAAK,QAAQ;AAAA,QACxI;AACA,gBAAQ;AAAA,UACN,YAAY,KAAK;AAAA,UACjB,UAAU,KAAK;AAAA,UACf;AAAA,UACA,MAAM;AAAA,QACR,CAAC;AAAA,MACH,GAAG,mBAAmB;AAEtB,YAAM,UAAU,CAAC,UAAkB;AACjC,sBAAc;AACd,cAAM,SAAS,gBAAgB,KAAK;AACpC,YAAI,CAAC,QAAQ;AACX,cAAI,kBAAkB,GAAG;AACvB,kBAAM,OAAO,MACV,SAAS,GAAG,KAAK,IAAI,IAAI,MAAM,MAAM,CAAC,EACtC,SAAS,KAAK;AACjB,mBAAO;AAAA,cACL,mCAAmC,MAAM,MAAM,SAAS,IAAI;AAAA,YAC9D;AAAA,UACF;AACA;AAAA,QACF;AACA,gBAAQ;AACR,gBAAQ;AAAA,UACN,YAAY,OAAO;AAAA,UACnB,UAAU,OAAO;AAAA,UACjB,WAAW,OAAO;AAAA,UAClB,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAEA,YAAM,UAAU,MAAM;AACpB,qBAAa,OAAO;AACpB,QAAC,aAAqB;AAAA,UACpB;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,MAAC,aAAqB,KAAK,cAAqB,OAAc;AAAA,IAChE,CAAC;AAAA,EACH;AAEA,UAAQ,MAAM,cAAc;AAE5B,QAAM,QAAwB;AAAA,IAC5B,WAAW,SAAS;AAAA,IACpB,aAAa;AAAA,IACb,GAAI,SAAS,cAAc,SACvB;AAAA,MACE,MAAM;AAAA,QACJ,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,QACpB,GAAI,SAAS,iBACT,EAAE,gBAAgB,SAAS,eAAe,IAC1C,CAAC;AAAA,MACP;AAAA,IACF,IACA;AAAA,MACE,MAAM;AAAA,QACJ,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,MACtB;AAAA,IACF;AAAA,EACN;AAEA,QAAM,WAAoC,QACtC;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,MAAM;AAAA,IAClB,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,EACnB,IACA;AAEJ,QAAM,MAAM,gBAAgB,OAAO,QAAQ;AAC3C,QAAM,YAAY,MAChB,IAAI;AAAA,IACF;AAAA,IACA;AAAA,IACA,WAAW,mBAAmB;AAAA,IAC9B;AAAA,EACF;AACF,MAAI,QAAQ,UAAU;AAEtB;AAAA,IACE,gBAAgB,SAAS,SAAS,QAAQ,GAAG,GAAG,WAAW,cAAc,SAAS,UAAU,IAAI,SAAS,QAAQ,KAAK,aAAa;AAAA,EACrI;AAEA,MAAI,aAAa;AACjB,QAAM,UAAU,oBAAI,IAAgB;AACpC,MAAI;AACJ,MAAI,cAAc;AAClB,MAAI,aAAa;AAGjB,MAAI,iBAAiB,KAAK,IAAI;AAC9B,QAAM,gBAAgB,MAAM;AAC1B,qBAAiB,KAAK,IAAI;AAAA,EAC5B;AAEA,QAAM,qBAAqB,MAAM;AAC/B,QAAI,CAAC,kBAAmB;AACxB,iBAAa,iBAAiB;AAC9B,wBAAoB;AAAA,EACtB;AAEA,MAAI;AACJ,QAAM,oBAAoB,MAAM;AAC9B,QAAI,CAAC,YAAa;AAClB,kBAAc,WAAW;AACzB,kBAAc;AAAA,EAChB;AACA,QAAM,qBAAqB,MAAM;AAC/B,QAAI,CAAC,mBAAmB,mBAAmB,EAAG;AAC9C,QAAI,YAAa;AACjB,UAAM,SAAS,KAAK;AAAA,MAClB;AAAA,MACA,KAAK,IAAI,KAAM,KAAK,MAAM,kBAAkB,CAAC,CAAC;AAAA,IAChD;AACA,kBAAc,YAAY,MAAM;AAC9B,UAAI,eAAe,WAAY;AAC/B,YAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,UAAI,UAAU,gBAAiB;AAC/B;AAAA,QACE,IAAI;AAAA,UACF,0BAA0B,OAAO,iBAAiB,eAAe;AAAA,QACnE;AAAA,MACF,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAClB,GAAG,MAAM;AAAA,EACX;AAEA,QAAM,uBAAuB,CAC3B,YACG;AACH,QAAI,CAAC,eAAgB;AACrB,QAAI,kBAAmB;AACvB,wBAAoB,WAAW,MAAM;AACnC,0BAAoB;AACpB,UAAI,eAAe;AACjB,gBAAQ,IAAI,MAAM,2BAA2B,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,IAClE,GAAG,cAAc;AAAA,EACnB;AAEA,QAAM,SAAS,QAAQ,aAAa;AAEpC,QAAM,UAAU,OAAO,WAAoC;AACzD,QAAI,YAAa;AACjB,QAAI,WAAY;AAChB,iBAAa;AACb,kBAAc;AAEd,uBAAmB;AAEnB,UAAM,UACH,QAAgB,WAAY,QAAgB,WAAW,KAAK;AAC/D,UAAMC,WAAU,OAAO,QAAQ;AAC/B,UAAM,UACJA,YAAW,OAAOA,aAAY,WAC1B,GAAGA,SAAQ,OAAO,IAAIA,SAAQ,IAAI,KAClC;AACN,QAAI;AACF;AAAA,QACE,qCAAqC,OAAO,YAAY,UAAU,WAAW,OAAO;AAAA,MACtF;AAAA;AAEA;AAAA,QACE,qCAAqC,OAAO,YAAY,UAAU;AAAA,MACpE;AAGF,eAAW,KAAK,MAAM,KAAK,OAAO,GAAG;AACnC,UAAI;AACF,UAAE,QAAQ;AAAA,MACZ,QAAQ;AAAA,MAER;AAAA,IACF;AACA,YAAQ,MAAM;AAEd,QAAI;AACF,YAAM,MAAM;AAAA,IACd,QAAQ;AAAA,IAER;AACA,YAAQ,UAAU;AAGlB,QAAI;AACF,YAAM,YAAY,KAAK;AAAA,IACzB,QAAQ;AAAA,IAER;AACA,QAAI;AACF,YAAM,YAAY,MAAM;AAAA,IAC1B,SAAS,GAAG;AAEV,mBAAa;AACb,YAAM,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AACvB;AAAA,IACF;AAIA,QAAI,mBAAmB;AAAA,IAGvB;AAEA,iBAAa;AACb,kBAAc;AACd,QAAI,mCAAmC;AAGvC,QAAI,eAAe,EAAG,sBAAqB,KAAK;AAAA,EAClD;AAEA,QAAM,QAAQ,OAAO,WAAoC;AACvD,QAAI,YAAa;AACjB,kBAAc;AAEd,sBAAkB;AAClB,uBAAmB;AACnB,UAAM,YACH,QAAgB,WAChB,QAAgB,WAAW,KAC5B,UACA;AAEF,UAAM,MAAM;AAEZ,QAAI;AACF,YAAM,YAAY,KAAK;AAAA,IACzB,QAAQ;AAAA,IAER;AAGA,QAAI,kBAAkB;AACpB,UAAI;AACF,cAAM,iBAAiB,QAAQ;AAC/B,2BAAmB,4BAA4B;AAAA,MACjD,QAAQ;AAAA,MAER;AAAA,IACF;AAKA,QAAI,sBAAsB,CAAC,kBAAkB;AAC3C,YAAM,QAAQ;AAAA,QACZ,MAAM,KAAK,WAAW,EAAE,IAAI,OAAO,MAAM;AACvC,cAAI;AACF,kBAAM,EAAE,MAAM;AAAA,UAChB,QAAQ;AAAA,UAER;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI;AACF,aAAO,MAAM;AAAA,IACf,QAAQ;AAAA,IAER;AAEA,QAAI,aAAa,SAAS,GAAG;AAAA,EAC/B;AAEA,SAAO,GAAG,cAAc,CAAC,WAAW;AAClC,kBAAc;AACd,UAAM,SAAS,GAAG,OAAO,iBAAiB,SAAS,IAAI,OAAO,cAAc,SAAS;AAErF,UAAM,cAAc,MAAM;AACxB;AACA,yBAAmB;AACnB,cAAQ,IAAI,MAAM;AAGlB,aAAO,GAAG,QAAQ,MAAM,cAAc,CAAC;AAGvC,UAAI;AACF,cAAM,YAAY,OAAO,MAAM,KAAK,MAAM;AAC1C,QAAC,OAAe,QAAQ,IAAI,SAAgB;AAC1C,wBAAc;AACd,iBAAO,UAAU,GAAG,IAAI;AAAA,QAC1B;AAAA,MACF,QAAQ;AAAA,MAER;AAEA,YAAM,UAAU,MAAM;AACtB,UAAI,qBAAqB,MAAM,UAAU,UAAU,GAAG;AAAA,IACxD;AAEA,QAAI,CAAC,aAAa;AAEhB,kBAAY;AAAA,IACd,OAAO;AAEL,UAAI,gBAAgB;AACpB,UAAI,aAAa,OAAO,MAAM,CAAC;AAC/B,YAAM,cAAc,WAAW,MAAM;AACnC,YAAI,CAAC,eAAe;AAClB,cAAI,yCAAyC,MAAM,GAAG;AACtD,iBAAO,QAAQ;AAAA,QACjB;AAAA,MACF,GAAG,GAAI;AAEP,YAAM,SAAS,CAAC,SAAiB;AAC/B,sBAAc;AAEd,YAAI,CAAC,eAAe;AAClB,uBAAa,OAAO,OAAO,CAAC,YAAY,IAAI,CAAC;AAC7C,gBAAM,aAAa,WAAW,SAAS,MAAM;AAC7C,gBAAM,YAAY,WAAW,MAAM,qBAAqB;AAExD,cAAI,WAAW;AACb,kBAAM,CAAC,EAAE,gBAAgB,cAAc,IAAI;AAC3C,gBAAI,mBAAmB,YAAY,mBAAmB,UAAU;AAC9D,8BAAgB;AAChB,2BAAa,WAAW;AACxB,0BAAY;AAGZ,oBAAM,iBAAiB,UAAU,CAAC,EAAE;AACpC,oBAAM,gBAAgB,WAAW,SAAS,cAAc;AAGxD,qBAAO,eAAe,QAAQ,MAAM;AACpC,qBAAO,GAAG,QAAQ,MAAM,cAAc,CAAC;AAGvC,kBAAI,cAAc,SAAS,GAAG;AAC5B,uBAAO,KAAK,QAAQ,aAAa;AAAA,cACnC;AAAA,YACF,OAAO;AACL,kBAAI,wCAAwC,MAAM,GAAG;AACrD,qBAAO,QAAQ;AACf;AAAA,YACF;AAAA,UACF,WAAW,WAAW,SAAS,MAAM;AAEnC,gBAAI,iDAAiD,MAAM,GAAG;AAC9D,mBAAO,QAAQ;AACf;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,aAAO,GAAG,QAAQ,MAAM;AAAA,IAC1B;AAEA,QAAI,UAAU;AACd,UAAM,MAAM,MAAM;AAChB,UAAI,CAAC,QAAS;AACd,gBAAU;AACV,mBAAa,KAAK,IAAI,GAAG,aAAa,CAAC;AACvC,cAAQ,OAAO,MAAM;AACrB,UAAI,wBAAwB,MAAM,UAAU,UAAU,GAAG;AACzD,UAAI,eAAe,EAAG,sBAAqB,KAAK;AAAA,IAClD;AAEA,WAAO,KAAK,SAAS,GAAG;AACxB,WAAO,KAAK,SAAS,GAAG;AAAA,EAC1B,CAAC;AAGD,MAAI,mBAAmB;AAErB,IAAC,YAAgC;AAAA,MAC/B;AAAA,MACA,CAAC,UAAkB;AACjB,sBAAc;AACd,YAAI;AAGF,cAAI,aAAa;AACjB,cAAI;AAEF,qBAAS,IAAI,GAAG,IAAI,MAAM,SAAS,GAAG,KAAK;AACzC,kBAAI,MAAM,CAAC,MAAM,KAAQ,MAAM,IAAI,CAAC,MAAM,GAAM;AAC9C,oBAAI,WAAW;AACf,oBAAI,MAAM,IAAI,CAAC,MAAM,GAAM;AACzB,6BAAW,IAAI;AAAA,gBACjB,WAAW,MAAM,IAAI,CAAC,MAAM,KAAQ,MAAM,IAAI,CAAC,MAAM,GAAM;AACzD,6BAAW,IAAI;AAAA,gBACjB;AAEA,oBAAI,YAAY,KAAK,WAAW,MAAM,QAAQ;AAC5C,wBAAM,WAAW,MAAM,QAAQ,KAAK,KAAK;AACzC,sBAAI,YAAY,GAAG;AAEjB,iCAAa;AACb;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AACA,gBAAM,oBAAoB,QAAQ,OAAO,YAAY,MAAS;AAAA,QAChE,SAAS,GAAG;AACV,gBAAM,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,MAAC,YAAgC;AAAA,QAC/B;AAAA,QACA,CAAC,UAAkB;AACjB,wBAAc;AACd,cAAI;AACF,gBAAI,OAAO,SAAS,QAAQ;AAC1B,oBAAM,mBAAmB,KAAK;AAAA,YAChC,OAAO;AACL,oBAAM,qBAAqB,KAAK;AAAA,YAClC;AAAA,UACF,SAAS,GAAG;AACV,kBAAM,CAAC,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AAEL,IAAC,YAAoC;AAAA,MACnC;AAAA,MACA,CAAC,OAAY;AACX,sBAAc;AACd,YAAI;AACF,gBAAM;AAAA,YACJ,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,UACL;AAAA,QACF,SAAS,GAAG;AACV,gBAAM,CAAC,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,MAAC,YAAoC;AAAA,QACnC;AAAA,QACA,CAAC,UAAkB;AACjB,wBAAc;AACd,cAAI;AACF,gBAAI,OAAO,SAAS,QAAQ;AAC1B,oBAAM,mBAAmB,KAAK;AAAA,YAChC,OAAO;AACL,oBAAM,qBAAqB,KAAK;AAAA,YAClC;AAAA,UACF,SAAS,GAAG;AACV,kBAAM,CAAC,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UACzB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,cAAY,GAAG,SAAgB,CAAC,MAAe;AAC7C,QAAI,WAAY;AAChB,UAAM,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzB,CAAC;AACD,cAAY,GAAG,SAAgB,CAAC,MAAe;AAC7C,QAAI,WAAY;AAChB,UAAM,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzB,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,GAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,EACxC,CAAC;AAED,QAAM,UAAU,OAAO,QAAQ;AAC/B,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,QAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,+BAA+B;AAE1D,MAAI,mBAAmB,IAAI,IAAI,IAAI,GAAG;AAEtC,QAAM,YAAY,WACd;AAAA,IACE,OAAO;AAAA,IACP,YAAY,SAAS;AAAA,IACrB,UAAU,SAAS;AAAA,EACrB,IACA;AAGJ,uBAAqB,KAAK;AAC1B,qBAAmB;AAEnB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,SAAS;AAAA,IACpB,GAAI,YAAY,EAAE,OAAO,UAAU,IAAI,CAAC;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA2EA,eAAsB,gCACpB,SAC8B;AAC9B,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,qBAAqB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,EACF,IAAI;AAGJ,QAAM,aACJ,QAAQ,eACP,SAAS,SAAS,SAAS,IAAI,cAAc;AAEhD,QAAM,WAAW,QAAQ;AAEzB,QAAM,MAAM,CAAC,QAAgB,SAC3B,OAAO;AAAA,IACL,sBAAsB,OAAO,SAAS,QAAQ,KAAK,GAAG;AAAA,IACtD,GAAG;AAAA,EACL;AACF,QAAM,OAAO,CAAC,QAAgB,SAC5B,OAAO;AAAA,IACL,sBAAsB,OAAO,SAAS,QAAQ,KAAK,GAAG;AAAA,IACtD,GAAG;AAAA,EACL;AAEF;AAAA,IACE,+BAA+B,UAAU,GAAG,WAAW,aAAa,QAAQ,KAAK,EAAE;AAAA,EACrF;AAGA,QAAM,eAQF;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,EACF;AACA,MAAI,UAAU,QAAW;AACvB,iBAAa,QAAQ;AAAA,EACvB;AACA,MAAI,aAAa,QAAW;AAC1B,iBAAa,WAAW;AAAA,EAC1B;AAEA,QAAM,EAAE,QAAQ,aAAa,MAAM,WAAW,IAC5C,MAAM,IAAI,2BAA2B,YAAY;AAGnD,MAAI;AASJ,QAAM,kBAAkB,YAMnB;AACH,WAAO,MAAM,IAAI,QAAQ,CAAC,SAAS,WAAW;AAC5C,YAAM,UAAU,WAAW,MAAM;AAC/B,gBAAQ;AACR;AAAA,UACE,IAAI;AAAA,YACF,gDAAgD,QAAQ;AAAA,UAC1D;AAAA,QACF;AAAA,MACF,GAAG,iBAAiB;AAEpB,YAAM,UAAU,CAAC,MAAe;AAC9B,gBAAQ;AACR,eAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,MACtD;AAEA,YAAM,OAAO,CAAC,OAAY;AACxB,YAAI,CAAC,IAAI,WAAY;AACrB,cAAM,YAAY,GAAG;AACrB,cAAM,aAAa,GAAG;AAEtB,YAAI,cAAc,QAAQ;AACxB,gBAAM,EAAE,KAAAF,MAAK,KAAAC,MAAK,eAAe,IAC/B,mCAAmC,UAAU;AAC/C,cAAI,CAACD,QAAO,CAACC,KAAK;AAClB,kBAAQ;AACR,kBAAQ;AAAA,YACN;AAAA,YACA;AAAA,YACA,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;AAAA,YAC3C,MAAM,EAAE,KAAAD,MAAK,KAAAC,KAAI;AAAA,UACnB,CAAC;AACD;AAAA,QACF;AAEA,cAAM,EAAE,KAAK,KAAK,IAAI,IACpB,mCAAmC,UAAU;AAC/C,YAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAK;AAC1B,gBAAQ;AACR,gBAAQ,EAAE,WAAW,YAAY,MAAM,EAAE,KAAK,KAAK,IAAI,EAAE,CAAC;AAAA,MAC5D;AAGA,YAAM,eAAe,CAAC,UAAkB;AACtC,YAAI,MAAO;AAEX,YAAI;AACF,gBAAM,OAAO,gBAAgB,KAAK;AAClC,cAAI,MAAM;AACR,oBAAQ;AAAA,cACN,YAAY,KAAK;AAAA,cACjB,UAAU,KAAK;AAAA,cACf,WAAW,KAAK;AAAA,cAChB,MAAM;AAAA,YACR;AACA;AAAA,cACE,+BAA+B,MAAM,UAAU,OAAO,MAAM,QAAQ;AAAA,YACtE;AAAA,UACF;AAAA,QACF,QAAQ;AAEN,cAAI,cAAc;AAChB,kBAAM,YAAY,+BAA+B;AAAA,cAC/C,YAAY,aAAa;AAAA,cACzB,UAAU,aAAa;AAAA,YACzB,CAAC;AACD,gBAAI,WAAW;AACb,sBAAQ;AAAA,gBACN,YAAY,aAAa;AAAA,gBACzB,UAAU,aAAa;AAAA,gBACvB;AAAA,gBACA,MAAM;AAAA,cACR;AACA;AAAA,gBACE,sBAAsB,MAAM,UAAU,OAAO,MAAM,QAAQ;AAAA,cAC7D;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,YAAM,UAAU,MAAM;AACpB,qBAAa,OAAO;AACpB,oBAAY,eAAe,SAAgB,OAAc;AACzD,oBAAY,eAAe,mBAA0B,IAAW;AAChE,oBAAY,eAAe,cAAqB,YAAmB;AAAA,MACrE;AAEA,kBAAY,GAAG,SAAgB,OAAc;AAC7C,kBAAY,GAAG,mBAA0B,IAAW;AACpD,kBAAY,GAAG,cAAqB,YAAmB;AAAA,IACzD,CAAC;AAAA,EACH;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,gBAAgB;AAAA,EACnC,SAAS,GAAG;AACV,QAAI;AACF,YAAM,WAAW;AAAA,IACnB,QAAQ;AAAA,IAER;AACA,QAAI,oBAAoB;AACtB,UAAI;AACF,cAAM,IAAI,MAAM;AAAA,MAClB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,UAAM;AAAA,EACR;AAEA,MAAI,yBAAyB,SAAS,SAAS,EAAE;AAEjD,QAAM,QAAwB;AAAA,IAC5B,WAAW,SAAS;AAAA,IACpB,aAAa;AAAA,IACb,GAAI,SAAS,cAAc,SACvB;AAAA,MACE,MAAM;AAAA,QACJ,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,QACpB,GAAI,SAAS,iBACT,EAAE,gBAAgB,SAAS,eAAe,IAC1C,CAAC;AAAA,MACP;AAAA,IACF,IACA;AAAA,MACE,MAAM;AAAA,QACJ,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,QACpB,KAAK,SAAS,KAAM;AAAA,MACtB;AAAA,IACF;AAAA,EACN;AAEA,QAAM,WAAoC,QACtC;AAAA,IACE,OAAO;AAAA,IACP,aAAa;AAAA,IACb,YAAY,MAAM;AAAA,IAClB,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM;AAAA,EACnB,IACA;AAEJ,QAAM,MAAM,gBAAgB,OAAO,QAAQ;AAC3C,QAAM,MAAM;AACZ,QAAM,QAAQ,IAAI;AAAA,IAChB;AAAA,IACA;AAAA,IACA,WAAW,mBAAmB;AAAA,IAC9B;AAAA,EACF;AAEA;AAAA,IACE,oBAAoB,SAAS,SAAS,aAAa,gBAAgB,GAAG,WAAW,eAAe,SAAS,UAAU,IAAI,SAAS,QAAQ,OAAO,gBAAgB,KAAK,cAAc;AAAA,EACpL;AAEA,MAAI,aAAa;AACjB,QAAM,UAAU,oBAAI,IAAgB;AACpC,MAAI,cAAc;AAClB,MAAI,cAAc;AAClB,MAAI;AAEJ,QAAM,QAAQ,OAAO,WAAoC;AACvD,QAAI,YAAa;AACjB,kBAAc;AAEd,UAAM,UACH,QAAgB,WAAY,QAAgB,WAAW,KAAK;AAC/D,QAAI,QAAS,KAAI,YAAY,OAAO,EAAE;AAAA,QACjC,KAAI,SAAS;AAElB,QAAI;AACF,YAAM,WAAW;AAAA,IACnB,QAAQ;AAAA,IAER;AAEA,eAAW,KAAK,SAAS;AACvB,UAAI;AACF,UAAE,QAAQ;AAAA,MACZ,QAAQ;AAAA,MAER;AAAA,IACF;AACA,YAAQ,MAAM;AAEd,QAAI;AACF,aAAO,MAAM;AAAA,IACf,QAAQ;AAAA,IAER;AAEA,QAAI,oBAAoB;AACtB,UAAI;AACF,cAAM,IAAI,MAAM;AAAA,MAClB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,aAAa;AAEpC,SAAO,GAAG,cAAc,CAAC,WAAW;AAClC,UAAM,SAAS,GAAG,OAAO,aAAa,IAAI,OAAO,UAAU;AAC3D,QAAI,qBAAqB,MAAM,EAAE;AAEjC,YAAQ,IAAI,MAAM;AAClB;AAEA,UAAM,cAAc,MAAM;AACxB,YAAM,UAAU,MAAM;AAAA,IACxB;AAEA,QAAI,CAAC,aAAa;AAEhB,kBAAY;AAAA,IACd,OAAO;AAEL,UAAI,gBAAgB;AACpB,UAAI,aAAa,OAAO,MAAM,CAAC;AAC/B,YAAM,cAAc,WAAW,MAAM;AACnC,YAAI,CAAC,eAAe;AAClB,cAAI,yCAAyC,MAAM,GAAG;AACtD,iBAAO,QAAQ;AAAA,QACjB;AAAA,MACF,GAAG,GAAI;AAEP,YAAM,SAAS,CAAC,SAAiB;AAC/B,YAAI,CAAC,eAAe;AAClB,uBAAa,OAAO,OAAO,CAAC,YAAY,IAAI,CAAC;AAC7C,gBAAM,aAAa,WAAW,SAAS,MAAM;AAC7C,gBAAM,YAAY,WAAW,MAAM,qBAAqB;AAExD,cAAI,WAAW;AACb,kBAAM,CAAC,EAAE,gBAAgB,cAAc,IAAI;AAC3C,gBAAI,mBAAmB,YAAY,mBAAmB,UAAU;AAC9D,8BAAgB;AAChB,2BAAa,WAAW;AACxB,0BAAY;AACZ,qBAAO,eAAe,QAAQ,MAAM;AAAA,YACtC,OAAO;AACL,kBAAI,wCAAwC,MAAM,GAAG;AACrD,qBAAO,QAAQ;AACf;AAAA,YACF;AAAA,UACF,WAAW,WAAW,SAAS,MAAM;AACnC,gBAAI,iDAAiD,MAAM,GAAG;AAC9D,mBAAO,QAAQ;AACf;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,aAAO,GAAG,QAAQ,MAAM;AAAA,IAC1B;AAEA,QAAI,UAAU;AACd,UAAM,MAAM,MAAM;AAChB,UAAI,CAAC,QAAS;AACd,gBAAU;AACV,mBAAa,KAAK,IAAI,GAAG,aAAa,CAAC;AACvC,cAAQ,OAAO,MAAM;AACrB,UAAI,+BAA+B,MAAM,YAAY,UAAU,GAAG;AAGlE,UAAI,eAAe,KAAK,aAAa;AACnC,cAAM,6BAA6B,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACrD;AAAA,IACF;AAEA,WAAO,KAAK,SAAS,GAAG;AACxB,WAAO,KAAK,SAAS,CAAC,MAAM;AAC1B,WAAK,wBAAwB,MAAM,IAAI,GAAG,WAAW,OAAO,CAAC,CAAC;AAC9D,UAAI;AAAA,IACN,CAAC;AAAA,EACH,CAAC;AAED,SAAO,GAAG,SAAS,CAAC,MAAM;AACxB,SAAK,gBAAgB,GAAG,WAAW,OAAO,CAAC,CAAC;AAC5C,UAAM,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzB,CAAC;AAGD,cAAY,GAAG,mBAA0B,CAAC,OAAY;AACpD,QAAI,YAAa;AACjB,QAAI;AACF,YAAM;AAAA,QACJ,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAG;AAAA,QACH,GAAG;AAAA,MACL;AAAA,IACF,SAAS,GAAG;AACV,YAAM,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACzB;AAAA,EACF,CAAC;AAGD,MAAI,UAAU;AACZ,gBAAY,GAAG,cAAqB,CAAC,UAAkB;AACrD,UAAI,YAAa;AACjB,UAAI;AACF,YAAI,OAAO,SAAS,QAAQ;AAC1B,gBAAM,mBAAmB,KAAK;AAAA,QAChC,OAAO;AACL,gBAAM,qBAAqB,KAAK;AAAA,QAClC;AAAA,MACF,SAAS,GAAG;AACV,cAAM,CAAC,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACzB;AAAA,IACF,CAAC;AAAA,EACH;AAGA,cAAY,GAAG,OAAc,MAAM;AACjC,QAAI,wBAAwB;AAC5B,kBAAc;AACd,0BAAsB;AAGtB,QAAI,eAAe,GAAG;AACpB,YAAM,cAAc,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACtC;AAAA,EACF,CAAC;AAED,cAAY,GAAG,SAAgB,CAAC,MAAe;AAC7C,UAAM,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACzB,CAAC;AAED,cAAY,GAAG,SAAgB,CAAC,MAAe;AAC7C,QAAI,CAAC,aAAa;AAChB,YAAM,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACzB;AAAA,EACF,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,GAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,EACxC,CAAC;AAED,QAAM,UAAU,OAAO,QAAQ;AAC/B,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,QAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,0CAA0C;AAErE,MAAI,mBAAmB,IAAI,IAAI,IAAI,GAAG;AAEtC,QAAM,YAAY,WACd;AAAA,IACE,OAAO;AAAA,IACP,YAAY,SAAS;AAAA,IACrB,UAAU,SAAS;AAAA,EACrB,IACA;AAEJ,QAAM,SAA8B;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,SAAS;AAAA,IACpB,GAAI,YAAY,EAAE,OAAO,UAAU,IAAI,CAAC;AAAA,IACxC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,SAAO,eAAe,QAAQ,eAAe;AAAA,IAC3C,KAAK,MAAM;AAAA,IACX,KAAK,CAAC,OAAiC;AACrC,4BAAsB;AAAA,IACxB;AAAA,EACF,CAAC;AAED,SAAO;AACT;;;AEviEA,OAAOE,WAAU;AACjB,SAAS,SAAAC,cAAgC;AACzC,SAAS,mBAA6B;AAsDtC,eAAsB,uBACpB,SAC2B;AAC3B,QAAM;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb,OAAO;AAAA,IACP;AAAA,EACF,IAAI;AAGJ,QAAM,aACJ,QAAQ,eACP,SAAS,SAAS,SAAS,IAAI,cAAc;AAEhD,QAAM,MAAM,CAAC,QACX,OAAO,IAAI,kBAAkB,OAAO,SAAS,SAAS,MAAM,GAAG,CAAC,KAAK,GAAG,EAAE;AAC5E,QAAM,OAAO,CAAC,QACZ,OAAO;AAAA,IACL,kBAAkB,OAAO,SAAS,SAAS,MAAM,GAAG,CAAC,KAAK,GAAG;AAAA,EAC/D;AAEF,MAAI,+BAA+B,UAAU,EAAE;AAG/C,QAAM,aAA4B,CAAC;AACnC,MAAI,cAAc;AAClB,MAAI,aAA8B;AAClC,MAAI,WAAW;AACf,MAAI,wBAAwB;AAG5B,QAAM,eAOF;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,EACF;AACA,MAAI,UAAU,QAAW;AACvB,iBAAa,QAAQ;AAAA,EACvB;AAEA,QAAM,EAAE,QAAQ,aAAa,MAAM,WAAW,IAC5C,MAAM,IAAI,2BAA2B,YAAY;AAGnD,QAAM,eAAe,CAAC,OAAY;AAChC,QAAI,CAAC,IAAI,KAAM;AAEf,UAAM,QAAqB;AAAA,MACzB,MAAM;AAAA,MACN,MAAM,GAAG;AAAA,MACT,YAAY,GAAG;AAAA,MACf,WAAW,GAAG;AAAA,MACd,WAAW,GAAG;AAAA,IAChB;AAEA,eAAW,KAAK,KAAK;AAErB,QAAI,GAAG,cAAc,QAAQ;AAC3B,mBAAa;AAAA,IACf;AAEA,QAAI,GAAG,cAAc,CAAC,uBAAuB;AAC3C,8BAAwB;AACxB;AAAA,QACE,kCAAkC,UAAU,YAAY,WAAW,MAAM;AAAA,MAC3E;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,CAAC,UAAkB;AACtC,QAAI,CAAC,OAAO,OAAQ;AACpB,eAAW;AAEX,eAAW,KAAK;AAAA,MACd,MAAM;AAAA,MACN,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,QAAM,cAAc,MAAM;AACxB,QAAI,sCAAsC,WAAW,MAAM,EAAE;AAC7D,kBAAc;AAAA,EAChB;AAEA,cAAY,GAAG,mBAA0B,YAAY;AACrD,cAAY,GAAG,cAAqB,YAAY;AAChD,cAAY,GAAG,OAAc,WAAW;AACxC,cAAY,GAAG,SAAgB,MAAM;AACnC,QAAI,CAAC,aAAa;AAChB,oBAAc;AAAA,IAChB;AAAA,EACF,CAAC;AAGD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,UAAU,WAAW,MAAM;AAC/B,aAAO,IAAI,MAAM,oCAAoC,CAAC;AAAA,IACxD,GAAG,GAAM;AAET,UAAM,QAAQ,MAAM;AAClB,UAAI,uBAAuB;AACzB,qBAAa,OAAO;AACpB,gBAAQ;AAAA,MACV,WAAW,aAAa;AACtB,qBAAa,OAAO;AACpB,eAAO,IAAI,MAAM,oCAAoC,CAAC;AAAA,MACxD,OAAO;AACL,mBAAW,OAAO,EAAE;AAAA,MACtB;AAAA,IACF;AACA,UAAM;AAAA,EACR,CAAC;AAED;AAAA,IACE,yBAAyB,UAAU,cAAc,QAAQ,YAAY,WAAW,MAAM;AAAA,EACxF;AAGA,MAAI,aAAiC;AACrC,MAAI,gBAAqC;AACzC,MAAI,SAAS;AAEb,QAAM,QAAQ,YAAY;AACxB,QAAI,OAAQ;AACZ,aAAS;AAET,QAAI,YAAY;AAEhB,gBAAY,eAAe,mBAA0B,YAAY;AACjE,gBAAY,eAAe,cAAqB,YAAY;AAC5D,gBAAY,eAAe,OAAc,WAAW;AAEpD,QAAI;AACF,YAAM,WAAW;AAAA,IACnB,QAAQ;AAAA,IAER;AAEA,QAAI,eAAe;AACjB,UAAI;AACF,sBAAc,KAAK,SAAS;AAAA,MAC9B,QAAQ;AAAA,MAER;AACA,sBAAgB;AAAA,IAClB;AAEA,QAAI,YAAY;AACd,iBAAW,MAAM;AACjB,mBAAa;AAAA,IACf;AAAA,EACF;AAGA,eAAaD,MAAK,aAAa,CAAC,KAAK,QAAQ;AAC3C,QAAI,QAAQ;AACV,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,eAAe;AACvB;AAAA,IACF;AAEA,QAAI,iBAAiB,IAAI,MAAM,IAAI,IAAI,GAAG,EAAE;AAG5C,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB;AAAA,MAChB,qBAAqB;AAAA,MACrB,iBAAiB;AAAA,MACjB,YAAY;AAAA,IACd,CAAC;AAGD,UAAM,eAAe,IAAI,YAAY;AAKrC,UAAM,aAAa,eAAe,SAAS,SAAS;AACpD,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,QAAI,oBAAoB,UAAU,IAAI,WAAW,KAAK,GAAG,CAAC,EAAE;AAE5D,oBAAgBC,OAAM,YAAY,YAAY;AAAA,MAC5C,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAGD,kBAAc,QAAQ,KAAK,YAAY,EAAE,KAAK,GAAG;AAGjD,kBAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACjD,YAAM,MAAM,KAAK,SAAS,EAAE,KAAK;AACjC,UAAI,KAAK;AACP,aAAK,WAAW,GAAG,EAAE;AAAA,MACvB;AAAA,IACF,CAAC;AAED,kBAAc,GAAG,SAAS,CAAC,QAAQ;AACjC,WAAK,iBAAiB,IAAI,OAAO,EAAE;AACnC,UAAI,IAAI;AAAA,IACV,CAAC;AAED,kBAAc,GAAG,SAAS,CAAC,SAAS;AAClC,UAAI,2BAA2B,IAAI,EAAE;AACrC,UAAI,IAAI;AAAA,IACV,CAAC;AAGD,QAAI,GAAG,SAAS,MAAM;AACpB,UAAI,qBAAqB;AACzB,UAAI,eAAe;AACjB,YAAI;AACF,wBAAc,OAAO,IAAI;AACzB,wBAAc,KAAK,SAAS;AAAA,QAC9B,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM,aAAa,YAAY;AAC7B,YAAM,QAAQ,eAAe;AAC7B,UAAI,CAAC,MAAO;AAGZ,UAAI,aAAa;AACjB,iBAAW,SAAS,YAAY;AAC9B,YAAI,MAAM,SAAS,WAAW,MAAM,MAAM;AACxC,cAAI;AACF,kBAAM,WAAW,MAAM,MAAM,MAAM,IAAI;AACvC,gBAAI,CAAC,UAAU;AACb,oBAAM,IAAI;AAAA,gBAAc,CAAC,YACvB,MAAM,KAAK,SAAS,OAAO;AAAA,cAC7B;AAAA,YACF;AAAA,UACF,SAAS,GAAG;AACV,iBAAK,8BAA8B,UAAU,KAAK,CAAC,EAAE;AACrD;AAAA,UACF;AAAA,QACF;AACA;AAAA,MACF;AAEA,UAAI,OAAO,UAAU,0BAA0B;AAG/C,YAAM,mBAAmB,CAAC,OAAY;AACpC,YAAI,CAAC,IAAI,QAAQ,CAAC,MAAM,SAAU;AAClC,YAAI;AACF,gBAAM,MAAM,GAAG,IAAI;AAAA,QACrB,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,kBAAY,GAAG,mBAA0B,gBAAgB;AAGzD,YAAM,aAAa,MAAM;AACvB,YAAI,uCAAuC;AAC3C,oBAAY,eAAe,mBAA0B,gBAAgB;AACrE,YAAI;AACF,gBAAM,IAAI;AAAA,QACZ,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,UAAI,aAAa;AACf,mBAAW;AAAA,MACb,OAAO;AACL,oBAAY,KAAK,OAAc,UAAU;AACzC,oBAAY,KAAK,SAAgB,UAAU;AAAA,MAC7C;AAAA,IACF;AAGA,eAAW,EAAE,MAAM,CAAC,MAAM;AACxB,WAAK,yBAAyB,CAAC,EAAE;AAAA,IACnC,CAAC;AAAA,EACH,CAAC;AAGD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,eAAY,KAAK,SAAS,MAAM;AAChC,eAAY,OAAO,GAAG,MAAM,MAAM,QAAQ,CAAC;AAAA,EAC7C,CAAC;AAED,QAAM,UAAU,WAAW,QAAQ;AACnC,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AACA,QAAM,OAAO,QAAQ;AAErB,QAAM,MAAM,UAAU,IAAI,IAAI,IAAI;AAClC,MAAI,0BAA0B,GAAG,EAAE;AAEnC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AClXA,SAAS,gBAAAC,qBAAoB;AAC7B,YAAY,SAAS;AAkBrB,IAAM,oBAAN,MAA2B;AAAA,EACR;AAAA,EACA,QAAa,CAAC;AAAA,EACvB;AAAA,EAGA,SAAS;AAAA,EAEjB,YAAY,UAAkB;AAC5B,SAAK,WAAW,KAAK,IAAI,GAAG,WAAW,CAAC;AAAA,EAC1C;AAAA,EAEA,KAAK,MAAe;AAClB,QAAI,KAAK,OAAQ;AACjB,QAAI,KAAK,SAAS;AAChB,YAAM,EAAE,QAAQ,IAAI,KAAK;AACzB,WAAK,UAAU;AACf,cAAQ,EAAE,OAAO,MAAM,MAAM,MAAM,CAAC;AACpC;AAAA,IACF;AACA,SAAK,MAAM,KAAK,IAAI;AACpB,QAAI,KAAK,MAAM,SAAS,KAAK,UAAU;AACrC,WAAK,MAAM,OAAO,GAAG,KAAK,MAAM,SAAS,KAAK,QAAQ;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,QAAI,KAAK,SAAS;AAChB,YAAM,EAAE,QAAQ,IAAI,KAAK;AACzB,WAAK,UAAU;AACf,cAAQ,EAAE,OAAO,QAAkB,MAAM,KAAK,CAAC;AAAA,IACjD;AAAA,EACF;AAAA,EAEA,MAAM,OAAmC;AACvC,QAAI,KAAK,OAAQ,QAAO,EAAE,OAAO,QAAkB,MAAM,KAAK;AAC9D,UAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,QAAI,SAAS,OAAW,QAAO,EAAE,OAAO,MAAM,MAAM,MAAM;AAC1D,WAAO,MAAM,IAAI,QAA2B,CAAC,YAAY;AACvD,WAAK,UAAU,EAAE,QAAQ;AAAA,IAC3B,CAAC;AAAA,EACH;AACF;AAwBA,IAAM,qBAAN,MAAyB;AAAA,EACN;AAAA,EACA,SAAS,oBAAI,IAA4C;AAAA,EAClE,SAA4D;AAAA,EAC5D,UAAU;AAAA,EACV,cAAoC;AAAA,EAE5C,YAAY,MAAqB;AAC/B,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,QAAc;AACZ,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,SAAK,SAAS,KAAK,KAAK,aAAa;AAErC,SAAK,eAAe,YAAY;AAC9B,UAAI;AACF,yBAAiB,SAAS,KAAK,QAAS;AACtC,cAAI;AACF,iBAAK,KAAK,UAAU,KAAK;AAAA,UAC3B,QAAQ;AAAA,UAER;AACA,qBAAW,KAAK,KAAK,OAAO,OAAO,GAAG;AACpC,cAAE,KAAK,KAAK;AAAA,UACd;AAAA,QACF;AAAA,MACF,SAAS,GAAG;AACV,aAAK,KAAK,UAAU,CAAC;AAAA,MACvB,UAAE;AACA,mBAAW,KAAK,KAAK,OAAO,OAAO,EAAG,GAAE,MAAM;AAC9C,aAAK,OAAO,MAAM;AAClB,aAAK,UAAU;AACf,aAAK,KAAK,QAAQ;AAAA,MACpB;AAAA,IACF,GAAG;AAAA,EACL;AAAA,EAEA,UAAU,IAAwD;AAChE,UAAM,IAAI,IAAI,kBAA+B,KAAK,KAAK,aAAa;AACpE,SAAK,OAAO,IAAI,IAAI,CAAC;AACrB,UAAM,OAAO;AACb,YAAQ,mBAAmB;AACzB,UAAI;AACF,eAAO,MAAM;AACX,gBAAM,IAAI,MAAM,EAAE,KAAK;AACvB,cAAI,EAAE,KAAM;AACZ,gBAAM,EAAE;AAAA,QACV;AAAA,MACF,UAAE;AACA,UAAE,MAAM;AACR,aAAK,OAAO,OAAO,EAAE;AAAA,MACvB;AAAA,IACF,GAAG;AAAA,EACL;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,UAAU;AACf,UAAM,MAAM,KAAK;AACjB,SAAK,SAAS;AACd,eAAW,KAAK,KAAK,OAAO,OAAO,EAAG,GAAE,MAAM;AAC9C,SAAK,OAAO,MAAM;AAClB,QAAI;AACF,YAAM,KAAK,OAAO,MAAgB;AAAA,IACpC,QAAQ;AAAA,IAER;AACA,QAAI;AACF,YAAM,KAAK;AAAA,IACb,QAAQ;AAAA,IAER;AACA,SAAK,cAAc;AAAA,EACrB;AACF;AAoEO,IAAM,kBAAN,MAAM,yBAAwBC,cAMlC;AAAA,EACgB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,SAAS;AAAA,EACT;AAAA,EACA;AAAA;AAAA,EAGA,eAA0C;AAAA,EAC1C,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKrB,uBAAuB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKvB;AAAA,EACA,2BAA2B;AAAA,EAC3B;AAAA,EACA;AAAA;AAAA,EAGA,mBAAmB,oBAAI,IAAY;AAAA,EACnC,gBAAgB,oBAAI,IAAwB;AAAA,EAC5C;AAAA;AAAA,EAGA,cAAc;AAAA,EACd;AAAA,EACA,sBAAsB;AAAA,EACtB,0BAA0B;AAAA;AAAA,EAG1B,YAA8B,CAAC;AAAA;AAAA;AAAA,EAI/B,YAKG;AAAA,EAEX,YAAY,SAAiC;AAC3C,UAAM;AACN,SAAK,MAAM,QAAQ;AACnB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,SAAS,QAAQ,UAAU;AAChC,SAAK,WAAW,QAAQ;AACxB,SAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,SAAK,iBAAiB,QAAQ,eAAe;AAC7C,SAAK,iBAAiB,QAAQ,kBAAkB;AAChD,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,iBAAiB,QAAQ,kBAAkB;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AAEd,SAAK,SAAa,iBAAa,CAAC,WAAW,KAAK,aAAa,MAAM,CAAC;AACpE,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,OAAO,QAAQ,mCAAmC,IAAI,OAAO,EAAE;AACpE,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB,CAAC;AAED,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAK,OAAQ,OAAO,KAAK,YAAY,KAAK,YAAY,MAAM;AAC1D,cAAM,OAAO,KAAK,OAAQ,QAAQ;AAClC,aAAK,eAAe,KAAK;AACzB,aAAK,OAAO;AAAA,UACV,kCAAkC,KAAK,OAAO,IAAI,KAAK,IAAI,aAAa,KAAK,OAAO,YAAY,KAAK,OAAO;AAAA,QAC9G;AACA,aAAK,KAAK,aAAa,EAAE,MAAM,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAC9D,gBAAQ;AAAA,MACV,CAAC;AACD,WAAK,OAAQ,KAAK,SAAS,MAAM;AAAA,IACnC,CAAC;AAID,QAAI,KAAK,gBAAgB;AACvB,WAAK,OAAO;AAAA,QACV,yDAAyD,KAAK,OAAO,YAAY,KAAK,OAAO;AAAA,MAC/F;AACA,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,OAAQ;AAClB,SAAK,SAAS;AACd,iBAAa,KAAK,cAAc;AAChC,SAAK,uBAAuB;AAC5B,SAAK,wBAAwB;AAG7B,eAAW,CAAC,IAAI,IAAI,KAAK,KAAK,eAAe;AAC3C,WAAK,QAAQ;AACb,WAAK,iBAAiB,OAAO,EAAE;AAAA,IACjC;AACA,SAAK,cAAc,MAAM;AAGzB,UAAM,KAAK,iBAAiB;AAG5B,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAK,OAAQ,MAAM,MAAM,QAAQ,CAAC;AAAA,MACpC,CAAC;AACD,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,YAAY,CAAC;AAClB,SAAK,eAAe;AACpB,SAAK,KAAK,OAAO;AAAA,EACnB;AAAA;AAAA,EAGA,IAAI,OAA2B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,kBAAsC;AACxC,QAAI,KAAK,gBAAgB,KAAM,QAAO;AACtC,WAAO,mBAAmB,KAAK,YAAY;AAAA,EAC7C;AAAA;AAAA,EAGA,IAAI,cAAsB;AACxB,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,oBACJ,IACqD;AACrD,SAAK,iBAAiB,IAAI,QAAQ,EAAE,EAAE;AACtC,QAAI,CAAC,KAAK,oBAAoB;AAC5B,YAAM,KAAK,kBAAkB;AAAA,IAC/B;AACA,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,iBAAiB,OAAO,QAAQ,EAAE,EAAE;AACzC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,aAAa,UAAU,QAAQ,EAAE,EAAE;AAAA,EACjD;AAAA;AAAA,EAGA,sBAAsB,IAAkB;AACtC,SAAK,aAAa,QAAQ,EAAE,IAAI,wBAAwB;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAKS;AACP,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,QAA0B;AAC7C,UAAM,WAAW,GAAG,OAAO,aAAa,IAAI,OAAO,UAAU;AAC7D,WAAO,WAAW,IAAI;AAEtB,SAAK,iBAAiB,IAAI,QAAQ;AAClC,SAAK,cAAc,IAAI,UAAU,MAAM;AACvC,SAAK,OAAO;AAAA,MACV,0CAA0C,QAAQ,UAAU,KAAK,iBAAiB,IAAI;AAAA,IACxF;AACA,SAAK,KAAK,UAAU,QAAQ;AAG5B,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AAOA,QAAI,CAAC,KAAK,oBAAoB;AAC5B,WAAK,kBAAkB;AAAA,IACzB;AAGA,SAAK,WAAW,UAAU,MAAM,EAAE,MAAM,CAAC,QAAQ;AAC/C,WAAK,OAAO;AAAA,QACV,0CAA0C,QAAQ,KAAK,GAAG;AAAA,MAC5D;AAAA,IACF,CAAC;AAED,UAAM,UAAU,CAAC,WAAoB;AACnC,WAAK,aAAa,UAAU,MAAM;AAClC,aAAO,QAAQ;AAAA,IACjB;AACA,WAAO,GAAG,SAAS,CAAC,QAAQ,QAAQ,UAAU,IAAI,OAAO,EAAE,CAAC;AAC5D,WAAO,GAAG,SAAS,CAAC,aAAa,QAAQ,WAAW,uBAAuB,eAAe,CAAC;AAAA,EAC7F;AAAA,EAEA,MAAc,WAAW,UAAkB,QAAmC;AAG5E,UAAM,iBAAiB,KAAK,IAAI,IAAI;AACpC,WAAO,KAAK,UAAU,CAAC,KAAK,cAAc;AACxC,UAAI,OAAO,UAAW;AACtB,UAAI,KAAK,IAAI,IAAI,gBAAgB;AAC/B,aAAK,OAAO;AAAA,UACV,iEAAiE,QAAQ;AAAA,QAC3E;AACA;AAAA,MACF;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,IAC7C;AACA,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,aAAc;AAExC,UAAM,eAAe,KAAK,aAAa,UAAU,QAAQ;AAIzD,QAAI,QAA4B;AAGhC,UAAM,gBAAgB,KAAK,UAAU,MAAM;AAC3C,QAAI,aAAa;AACjB,aAAS,IAAI,cAAc,SAAS,GAAG,KAAK,GAAG,KAAK;AAClD,UAAI,cAAc,CAAC,EAAG,YAAY;AAChC,qBAAa;AACb;AAAA,MACF;AAAA,IACF;AAEA,QAAI,cAAc,GAAG;AACnB,YAAM,SAAS,cAAc,MAAM,UAAU;AAC7C,WAAK,OAAO;AAAA,QACV,8CAA8C,QAAQ,WAAW,OAAO,MAAM;AAAA,MAChF;AAEA,UAAI,CAAC,OAAO;AACV,gBAAQ,IAAI,YAAY;AAAA,UACtB,WAAW,KAAK,qBAAqB;AAAA,UACrC,cAAc;AAAA,QAChB,CAAC;AAAA,MACH;AACA,iBAAW,SAAS,QAAQ;AAC1B,YAAI,OAAO,UAAW;AACtB,YAAI;AACJ,YAAI,CAAC,MAAM,OAAO;AAChB,eAAK,MAAM,SAAS,MAAM,MAAM,MAAM,KAAK,MAAM,UAAU;AAAA,QAC7D,OAAO;AACL,eAAK,MAAM,SAAS,MAAM,MAAM,MAAM,GAAG;AAAA,QAC3C;AACA,YAAI,GAAG,SAAS,EAAG,QAAO,MAAM,EAAE;AAAA,MACpC;AAAA,IACF;AAGA,QAAI,eAAe,cAAc;AACjC,QAAI,iBAAiB;AACrB,QAAI,mBAAmB;AACvB,QAAI,YAAY,KAAK,IAAI;AACzB,QAAI;AACF,WAAK,OAAO;AAAA,QACV,gDAAgD,QAAQ,iBAAiB,YAAY;AAAA,MACvF;AACA,uBAAiB,SAAS,cAAc;AACtC,YAAI,OAAO,aAAa,CAAC,KAAK,QAAQ;AACpC,eAAK,OAAO;AAAA,YACV,4CAA4C,QAAQ,cAAc,OAAO,SAAS,WAAW,KAAK,MAAM;AAAA,UAC1G;AACA;AAAA,QACF;AAEA;AAEA,YAAI,MAAM,OAAO;AAEf,cAAI,OAAO;AACT,kBAAMC,OAAM,MAAM,gBAAgB,KAAK,IAAI,IAAI;AAC/C,kBAAMC,MAAK,MAAM,SAAS,MAAM,MAAMD,IAAG;AACzC,gBAAIC,IAAG,SAAS,EAAG,QAAO,MAAMA,GAAE;AAAA,UACpC;AACA;AAAA,QACF;AAGA,cAAM,SAAS,KAAK,kBAAkB,KAAK;AAC3C,YAAI,CAAC,OAAQ;AAEb,cAAM,OAAO,KAAK,iBAAiB,QAAQ,MAAM,SAAS;AAG1D,YAAI,CAAC,cAAc;AACjB,cAAI,CAAC,KAAM;AACX,yBAAe;AACf,eAAK,OAAO;AAAA,YACV,iDAAiD,QAAQ,UAAU,cAAc;AAAA,UACnF;AAEA,cAAI,CAAC,OAAO;AACV,oBAAQ,IAAI,YAAY;AAAA,cACtB,WAAW,MAAM,aAAa,KAAK,qBAAqB;AAAA,cACxD,cAAc;AAAA,YAChB,CAAC;AAAA,UACH;AAAA,QACF;AAEA,cAAM,MAAM,MAAM,gBAAgB,KAAK,IAAI,IAAI;AAC/C,cAAM,KAAK,MAAO,SAAS,QAAQ,KAAK,IAAI;AAC5C,eAAO,MAAM,EAAE;AAEf;AACA,aAAK;AAGL,YAAI,KAAK,IAAI,IAAI,YAAY,KAAQ;AACnC,eAAK,OAAO;AAAA,YACV,wCAAwC,QAAQ,aAAa,cAAc,YAAY,gBAAgB,WAAW,OAAO,cAAc;AAAA,UACzI;AACA,sBAAY,KAAK,IAAI;AAAA,QACvB;AAGA,YAAI,OAAO,iBAAiB,KAAK,gBAAgB;AAC/C,eAAK,OAAO;AAAA,YACV,sCAAsC,OAAO,cAAc,4BAA4B,QAAQ;AAAA,UACjG;AACA,iBAAO,QAAQ;AACf;AAAA,QACF;AAAA,MACF;AACA,WAAK,OAAO;AAAA,QACV,uDAAuD,QAAQ,aAAa,cAAc,YAAY,gBAAgB;AAAA,MACxH;AAAA,IACF,UAAE;AACA,YAAM,aAAa,OAAO,MAAgB,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,kBAAkB,OAAmC;AAC3D,QAAI,MAAM,MAAO,QAAO;AACxB,QAAI,MAAM,KAAK,WAAW,EAAG,QAAO;AACpC,QAAI;AACF,UAAI,MAAM,cAAc,QAAQ;AAC9B,eAAO,gBAAoB,MAAM,IAAI;AAAA,MACvC;AACA,UAAI,MAAM,cAAc,QAAQ;AAC9B,eAAOC,iBAAoB,MAAM,IAAI;AAAA,MACvC;AAAA,IACF,QAAQ;AAAA,IAER;AAEA,WAAO,MAAM;AAAA,EACf;AAAA;AAAA,EAGQ,iBACN,QACA,WACS;AACT,QAAI;AACF,UAAI,cAAc,QAAQ;AACxB,cAAM,OAAO,iBAAgB,gBAAgB,MAAM;AACnD,eAAO,KAAK,KAAK,CAAC,MAAM,EAAE,UAAU,MAAM,EAAE,CAAC,IAAK,QAAU,CAAC;AAAA,MAC/D;AACA,UAAI,cAAc,QAAQ;AACxB,cAAM,OAAOC,0BAAyB,MAAM;AAC5C,eAAO,KAAK;AAAA,UACV,CAAC,MAAM,EAAE,UAAU,KAAK,WAAY,EAAE,CAAC,KAAM,IAAK,EAAI;AAAA,QACxD;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,OAAe,gBAAgB,KAAuB;AACpD,UAAM,OAAiB,CAAC;AACxB,QAAI,IAAI;AACR,WAAO,IAAI,IAAI,QAAQ;AAErB,UACE,IAAI,IAAI,IAAI,UACZ,IAAI,CAAC,MAAM,KACX,IAAI,IAAI,CAAC,MAAM,GACf;AACA,YAAI;AACJ,YAAI,IAAI,IAAI,CAAC,MAAM,GAAG;AACpB,kBAAQ;AAAA,QACV,WAAW,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG;AACrE,kBAAQ;AAAA,QACV,OAAO;AACL;AACA;AAAA,QACF;AAEA,cAAM,WAAW,IAAI;AACrB,YAAI,SAAS,IAAI;AACjB,iBAAS,IAAI,UAAU,IAAI,IAAI,SAAS,GAAG,KAAK;AAC9C,cACE,IAAI,CAAC,MAAM,KACX,IAAI,IAAI,CAAC,MAAM,MACd,IAAI,IAAI,CAAC,MAAM,KAAM,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,IAC/E;AACA,qBAAS;AACT;AAAA,UACF;AAAA,QACF;AACA,YAAI,SAAS,UAAU;AACrB,eAAK,KAAK,IAAI,SAAS,UAAU,MAAM,CAAC;AAAA,QAC1C;AACA,YAAI;AAAA,MACN,OAAO;AACL;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAe,eAAe,GAAoB;AAChD,WAAO,EAAE,UAAU,KAAK,EAAE,CAAC,MAAM,QAAS,EAAE,CAAC,IAAK,SAAU;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAe,sBACb,GACoE;AACpE,QAAI,EAAE,SAAS,EAAG,QAAO;AACzB,QAAI,CAAC,iBAAgB,eAAe,CAAC,EAAG,QAAO;AAE/C,UAAM,gBAAiB,EAAE,CAAC,KAAM,IAAK;AACrC,UAAM,cAAc;AAAA,MAClB;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAAO;AAAA,MAC/D;AAAA,MAAO;AAAA,MAAM;AAAA,IACf;AACA,UAAM,aAAa,YAAY,aAAa,KAAK;AACjD,QAAI,CAAC,WAAY,QAAO;AAExB,UAAM,iBAAkB,EAAE,CAAC,IAAK,MAAS,IAAO,EAAE,CAAC,KAAM,IAAK;AAC9D,UAAM,WAAW,kBAAkB,IAAI,IAAI;AAE3C,UAAM,UAAW,EAAE,CAAC,KAAM,IAAK;AAC/B,UAAM,kBAAkB,UAAU;AAClC,UAAM,MACH,mBAAmB,KAAO,iBAAiB,IAAM,iBAAiB;AACrE,UAAM,YAAY,OAAO,KAAK,CAAE,OAAO,IAAK,KAAM,MAAM,GAAI,CAAC,EAAE;AAAA,MAC7D;AAAA,IACF;AACA,WAAO,EAAE,YAAY,UAAU,UAAU;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,0BAA0B,QAAsB;AACtD,QAAI,CAAC,KAAK,OAAQ;AAClB,QAAI,KAAK,qBAAsB;AAC/B,QAAI,KAAK,uBAAwB;AAEjC,UAAM,QAAQ,KAAK,2BAA2B,IAC1C,KAAK,2BACL;AACJ,SAAK,OAAO;AAAA,MACV,wDAAwD,QAAQ,KAAM,QAAQ,CAAC,CAAC,aAAa,MAAM;AAAA,IACrG;AACA,SAAK,yBAAyB,WAAW,MAAM;AAC7C,WAAK,yBAAyB;AAC9B,UAAI,CAAC,KAAK,OAAQ;AAClB,UAAI,KAAK,qBAAsB;AAC/B,WAAK,kBAAkB,EAAE,MAAM,CAAC,QAAQ;AACtC,aAAK,OAAO;AAAA,UACV,uDAAuD,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,QACjG;AAAA,MACF,CAAC;AAAA,IACH,GAAG,KAAK;AAGR,SAAK,2BAA2B,KAAK,IAAI,QAAQ,GAAG,GAAM;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,yBAA+B;AACrC,QAAI,KAAK,wBAAwB;AAC/B,mBAAa,KAAK,sBAAsB;AACxC,WAAK,yBAAyB;AAAA,IAChC;AACA,SAAK,2BAA2B;AAAA,EAClC;AAAA,EAEA,MAAc,oBAAmC;AAC/C,QAAI,KAAK,mBAAoB;AAM7B,QAAI,CAAC,KAAK,IAAI,SAAS;AACrB,UAAI,KAAK,IAAI,UAAU;AACrB,aAAK,OAAO;AAAA,UACV;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI;AACF,aAAK,OAAO;AAAA,UACV;AAAA,QACF;AACA,cAAM,KAAK,IAAI,gBAAgB;AAAA,MACjC,SAAS,GAAG;AACV,aAAK,OAAO;AAAA,UACV,6CAA6C,CAAC;AAAA,QAChD;AAIA,aAAK,0BAA0B,wBAAwB;AACvD;AAAA,MACF;AAAA,IACF;AAEA,SAAK,qBAAqB;AAG1B,QAAI;AACJ,QAAI,KAAK,UAAU;AACjB,UAAI;AAKF,cAAM,UAAU,MAAM,KAAK,IAAI;AAAA,UAC7B,QAAQ,KAAK,QAAQ,MAAM,KAAK,OAAO,IAAI,KAAK,OAAO;AAAA,QACzD;AACA,0BAAkB,QAAQ;AAC1B,aAAK,0BAA0B,QAAQ;AAAA,MACzC,SAAS,GAAG;AACV,aAAK,OAAO;AAAA,UACV,+EAA+E,CAAC;AAAA,QAClF;AAAA,MACF;AAAA,IACF;AAEA,SAAK,OAAO;AAAA,MACV,qDAAqD,KAAK,OAAO,YAAY,KAAK,OAAO,cAAc,CAAC,CAAC,eAAe;AAAA,IAC1H;AAKA,QAAI,YAAY;AAEhB,SAAK,eAAe,IAAI,mBAAmB;AAAA,MACzC,eAAe;AAAA,MACf,cAAc,MACZ,mBAAmB,KAAK,KAAK,KAAK,SAAS,KAAK,SAAS;AAAA,QACvD,SAAS,KAAK;AAAA,QACd,GAAI,kBAAkB,EAAE,QAAQ,gBAAgB,IAAI,CAAC;AAAA,MACvD,CAAC;AAAA,MACH,SAAS,CAAC,UAAU;AAElB,YAAI,CAAC,WAAW;AAGd,eAAK,uBAAuB;AAAA,QAC9B;AACA,oBAAY;AACZ,aAAK,cAAc,KAAK,IAAI;AAC5B,aAAK;AAGL,YAAI,CAAC,MAAM,UAAU,MAAM,cAAc,UAAU,MAAM,cAAc,SAAS;AAC9E,eAAK,oBAAoB,MAAM;AAAA,QACjC;AAIA,YAAI;AACJ,YAAI;AAEJ,YAAI,MAAM,OAAO;AACf,cAAI,MAAM,KAAK,WAAW,EAAG;AAG7B,cAAI,CAAC,KAAK,WAAW;AACnB,kBAAM,SAAS,iBAAgB,sBAAsB,MAAM,IAAI;AAC/D,gBAAI,QAAQ;AACV,mBAAK,YAAY,EAAE,OAAO,YAAY,GAAG,OAAO;AAAA,YAClD;AAAA,UACF;AACA,uBAAa,MAAM;AACnB,uBAAa;AAAA,QACf,OAAO;AACL,gBAAM,SAAS,KAAK,kBAAkB,KAAK;AAC3C,cAAI,CAAC,UAAU,OAAO,WAAW,EAAG;AACpC,uBAAa;AACb,uBAAa,KAAK,iBAAiB,QAAQ,MAAM,SAAS;AAAA,QAC5D;AAEA,cAAM,MAAM,MAAM,gBAAgB,KAAK,IAAI,IAAI;AAC/C,aAAK,UAAU,KAAK;AAAA,UAClB,MAAM,OAAO,KAAK,UAAU;AAAA,UAC5B,MAAM,KAAK,IAAI;AAAA,UACf;AAAA,UACA,OAAO,MAAM;AAAA,UACb;AAAA,QACF,CAAC;AAGD,cAAM,SAAS,KAAK,IAAI,IAAI,KAAK;AACjC,YAAI,UAAU;AACd,eAAO,UAAU,KAAK,UAAU,UAAU,KAAK,UAAU,OAAO,EAAG,OAAO,QAAQ;AAChF;AAAA,QACF;AACA,YAAI,UAAU,EAAG,MAAK,UAAU,OAAO,GAAG,OAAO;AAAA,MACnD;AAAA,MACA,SAAS,CAAC,UAAU;AAClB,aAAK,OAAO,OAAO,0CAA0C,KAAK,EAAE;AAAA,MACtE;AAAA,MACA,OAAO,MAAM;AAKX,YAAI,KAAK,qBAAsB;AAC/B,aAAK,qBAAqB;AAC1B,aAAK,eAAe;AACpB,aAAK,wBAAwB;AAE7B,cAAM,YAAY,KAAK,cAAc,IAAI,KAAK,IAAI,IAAI,KAAK,cAAc;AACzE,cAAM,YAAY,YAAY,KAAK,kBAC/B,kCACA,aAAa,IACX,yBACA;AACN,aAAK,OAAO;AAAA,UACV,qDAAqD,SAAS,eACjD,aAAa,IAAI,IAAI,YAAY,KAAM,QAAQ,CAAC,CAAC,UAAU,OAAO,YACpE,KAAK,mBAAmB,YAAY,KAAK,iBAAiB,IAAI;AAAA,QAC3E;AAGA,YAAI,KAAK,yBAAyB;AAChC,eAAK,wBAAwB,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAC7C,eAAK,0BAA0B;AAAA,QACjC;AAaA,YAAI,CAAC,KAAK,gBAAgB;AACxB,eAAK,OAAO;AAAA,YACV,4DAA4D,SAAS,YAC1D,KAAK,OAAO,YAAY,KAAK,OAAO,oBAAe,KAAK,iBAAiB,IAAI;AAAA,UAC1F;AACA,qBAAW,CAAC,EAAE,IAAI,KAAK,KAAK,eAAe;AACzC,iBAAK,QAAQ;AAAA,UACf;AAAA,QACF,WAAW,KAAK,QAAQ;AAMtB,cACE,OAAO,KAAK,IAAI,4BAA4B,cAC5C,KAAK,IAAI,wBAAwB,KAAK,SAAS,KAAK,OAAO,GAC3D;AACA,iBAAK,OAAO;AAAA,cACV,yDAAyD,KAAK,OAAO,YAAY,KAAK,OAAO;AAAA,YAC/F;AACA,uBAAW,CAAC,EAAE,IAAI,KAAK,KAAK,eAAe;AACzC,mBAAK,QAAQ;AAAA,YACf;AACA;AAAA,UACF;AACA,eAAK,OAAO;AAAA,YACV,uDAAuD,KAAK,iBAAiB,IAAI,cAAc,KAAK,cAAc;AAAA,UACpH;AACA,eAAK,kBAAkB;AAAA,QACzB;AAAA,MACF;AAAA,IACF,CAAC;AAED,SAAK,aAAa,MAAM;AACxB,SAAK,yBAAyB;AAAA,EAChC;AAAA,EAEA,MAAc,mBAAkC;AAI9C,SAAK,uBAAuB;AAC5B,SAAK,qBAAqB;AAG1B,SAAK,uBAAuB;AAC5B,SAAK,wBAAwB;AAC7B,UAAM,SAAS,KAAK;AACpB,SAAK,eAAe;AACpB,QAAI;AACF,UAAI,QAAQ;AACV,cAAM,OAAO,KAAK;AAAA,MACpB;AACA,WAAK,YAAY,CAAC;AAElB,UAAI,KAAK,yBAAyB;AAChC,cAAM,KAAK,wBAAwB,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AACnD,aAAK,0BAA0B;AAAA,MACjC;AAAA,IACF,UAAE;AACA,WAAK,uBAAuB;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,2BAAiC;AACvC,SAAK,wBAAwB;AAC7B,QAAI,KAAK,mBAAmB,EAAG;AAE/B,SAAK,cAAc,KAAK,IAAI;AAC5B,SAAK,oBAAoB,YAAY,MAAM;AACzC,UAAI,CAAC,KAAK,sBAAsB,CAAC,KAAK,QAAQ;AAC5C,aAAK,wBAAwB;AAC7B;AAAA,MACF;AAEA,YAAM,YAAY,KAAK,IAAI,IAAI,KAAK;AACpC,UAAI,YAAY,KAAK,iBAAiB;AACpC,aAAK,OAAO;AAAA,UACV,+DAA+D,YAAY,KAAM,QAAQ,CAAC,CAAC,gBAAgB,KAAK,eAAe,sBAC9G,KAAK,mBAAmB,YAAY,KAAK,iBAAiB,IAAI;AAAA,QACjF;AACA,aAAK,wBAAwB;AAU7B,cAAM,SAAS,KAAK;AACpB,YAAI,QAAQ;AACV,eAAK,qBAAqB;AAC1B,eAAK,eAAe;AACpB,iBAAO,KAAK,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC9B;AAAA,MACF;AAAA,IACF,GAAG,KAAK,IAAI,KAAK,kBAAkB,GAAG,GAAK,CAAC;AAAA,EAC9C;AAAA,EAEQ,0BAAgC;AACtC,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,aAAa,UAAkB,QAAuB;AAC5D,QAAI,CAAC,KAAK,iBAAiB,IAAI,QAAQ,EAAG;AAC1C,SAAK,iBAAiB,OAAO,QAAQ;AACrC,SAAK,cAAc,OAAO,QAAQ;AAElC,UAAM,YAAY,KAAK,cAAc,IAAI,KAAK,IAAI,IAAI,KAAK,cAAc;AACzE,UAAM,cAAc,aAAa,IAAI,eAAe,YAAY,KAAM,QAAQ,CAAC,CAAC,UAAU;AAC1F,SAAK,OAAO;AAAA,MACV,6CAA6C,QAAQ,WAAW,UAAU,SAAS,eACpE,KAAK,iBAAiB,IAAI,aAAa,KAAK,mBAAmB,YAAY,KAAK,uBAAuB,GAAG,WAAW;AAAA,IACtI;AACA,SAAK,KAAK,sBAAsB,QAAQ;AAExC,QAAI,KAAK,iBAAiB,SAAS,KAAK,CAAC,KAAK,gBAAgB;AAG5D,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,eAAqB;AAC3B,QAAI,KAAK,eAAgB;AACzB,SAAK,OAAO;AAAA,MACV,2DAA2D,KAAK,aAAa;AAAA,IAC/E;AACA,SAAK,iBAAiB,WAAW,YAAY;AAC3C,WAAK,iBAAiB;AACtB,UAAI,KAAK,iBAAiB,SAAS,KAAK,KAAK,oBAAoB;AAC/D,aAAK,OAAO,OAAO,gEAAgE;AACnF,cAAM,KAAK,iBAAiB;AAAA,MAC9B;AAAA,IACF,GAAG,KAAK,aAAa;AAAA,EACvB;AACF;;;AC1nCA,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,SAAAC,cAAa;AACtB,YAAYC,WAAU;AAetB,IAAM,oBAAoB,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC;AAC9D,IAAM,oBAAoB,OAAO,KAAK,CAAC,GAAM,GAAM,CAAI,CAAC;AAExD,SAAS,eAAe,MAAuB;AAC7C,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,SAAO,KAAK,SAAS,GAAG,CAAC,EAAE,OAAO,iBAAiB,KAAK,KAAK,SAAS,GAAG,CAAC,EAAE,OAAO,iBAAiB;AACtG;AAEA,SAAS,gBAAgB,QAA0B;AAGjD,QAAM,SAA8C,CAAC;AACrD,WAAS,IAAI,GAAG,IAAI,OAAO,SAAS,GAAG,KAAK;AAC1C,QAAI,OAAO,CAAC,MAAM,KAAQ,OAAO,IAAI,CAAC,MAAM,GAAM;AAChD,UAAI,OAAO,IAAI,CAAC,MAAM,GAAM;AAC1B,eAAO,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,CAAC;AAC9B,aAAK;AAAA,MACP,WAAW,OAAO,IAAI,CAAC,MAAM,KAAQ,OAAO,IAAI,CAAC,MAAM,GAAM;AAC3D,eAAO,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,CAAC;AAC9B,aAAK;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAEjC,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,eAAe,MAAM,MAAM,MAAM;AACvC,UAAM,OAAO,OAAO,IAAI,CAAC;AACzB,UAAM,aAAa,OAAO,KAAK,MAAM,OAAO;AAC5C,QAAI,aAAa,aAAc,KAAI,KAAK,OAAO,SAAS,cAAc,UAAU,CAAC;AAAA,EACnF;AACA,SAAO;AACT;AAEA,SAAS,YAAY,YAAmC;AACtD,MAAI,WAAW,SAAS,EAAG,QAAO;AAClC,QAAM,KAAK,WAAW,CAAC;AACvB,MAAI,OAAO,OAAW,QAAO;AAC7B,SAAO,KAAK;AACd;AAEA,SAAS,yBAAyB,QAAyB;AACzD,QAAM,OAAO,gBAAgB,MAAM;AACnC,aAAW,OAAO,MAAM;AACtB,UAAM,IAAI,YAAY,GAAG;AACzB,QAAI,MAAM,EAAG,QAAO;AAAA,EACtB;AACA,SAAO;AACT;AAQO,IAAM,2BAAN,cAAuCF,cAI3C;AAAA,EACO;AAAA,EACA;AAAA,EACA;AAAA,EAAuB;AAAA,EAAyB;AAAA,EAChD;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,UAAU,oBAAI,IAAyB;AAAA,EACvC;AAAA,EAIA,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,YAA2B;AAAA;AAAA,EAC3B,YAA2B;AAAA;AAAA,EAEnC,YAAY,SAA0C;AACpD,UAAM;AACN,SAAK,cAAc,QAAQ;AAC3B,SAAK,aAAa,QAAQ,cAAc;AACxC,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,SAAS,QAAQ,UAAU;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,SAAK,OAAO,KAAK,8DAA8D;AAE/E,UAAM,KAAK,YAAY,MAAM;AAC7B,SAAK,OAAO,KAAK,0DAA0D;AAG3E,SAAK,aAAkB,mBAAa,CAAC,KAAK,QAAQ;AAChD,UAAI,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,GAAG,KAAK,IAAI,OAAO;AAC1D,aAAK,OAAO,KAAK,oDAAoD,IAAI,OAAO,aAAa,EAAE;AAC/F,aAAK,QAAQ,IAAI,GAAG;AACpB,aAAK,KAAK,UAAU,IAAI,OAAO,iBAAiB,SAAS;AAGzD,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,iBAAiB;AAAA,UACjB,cAAc;AAAA,UACd,+BAA+B;AAAA,QACjC,CAAC;AAGD,YAAI,GAAG,SAAS,MAAM;AACpB,eAAK,QAAQ,OAAO,GAAG;AACvB,eAAK,OAAO,KAAK,gDAAgD;AAAA,QACnE,CAAC;AAAA,MACH,OAAO;AACL,YAAI,UAAU,GAAG;AACjB,YAAI,IAAI,WAAW;AAAA,MACrB;AAAA,IACF,CAAC;AAGD,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAK,WAAY,OAAO,KAAK,YAAY,aAAa,MAAM;AAC1D,aAAK,OAAO,KAAK,4DAA4D,KAAK,UAAU,EAAE;AAC9F,gBAAQ;AAAA,MACV,CAAC;AACD,WAAK,WAAY,GAAG,SAAS,MAAM;AAAA,IACrC,CAAC;AAGD,SAAK,OAAO,KAAK,+EAA+E;AAEhG,UAAM,SAASC,OAAM,UAAU;AAAA,MAC7B;AAAA;AAAA;AAAA,MAGA;AAAA,MAAa;AAAA;AAAA,MAEb;AAAA,MAAM,OAAO,KAAK,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQ1B;AAAA,MAAW;AAAA,MACX;AAAA,MAAM;AAAA;AAAA,MACN;AAAA,MAAM;AAAA;AAAA,MACN;AAAA,MAAQ;AAAA;AAAA,MACR;AAAA,MAAe;AAAA,MACf;AAAA,MAAa;AAAA,MACb;AAAA,MAAM;AAAA;AAAA,MACN;AAAA;AAAA,IACF,GAAG;AAAA,MACD,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,SAAK,gBAAgB;AACrB,SAAK,OAAO,KAAK,2DAA2D,OAAO,GAAG,GAAG;AAGzF,QAAI,aAAa;AACjB,UAAM,gBAAgB,CAAC,cAAsB;AAG3C,UAAI,CAAC,eAAe,SAAS,GAAG;AAC9B;AAAA,MACF;AACA;AACA,UAAI,eAAe,GAAG;AACpB,aAAK,OAAO,KAAK,0DAA0D,UAAU,MAAM,SAAS;AAAA,MACtG;AACA,UAAI,OAAO,SAAS,CAAC,OAAO,MAAM,WAAW;AAC3C,YAAI;AACF,iBAAO,MAAM,MAAM,SAAS;AAAA,QAC9B,SAAS,OAAO;AACd,eAAK,OAAO,MAAM,mDAAmD,KAAK,EAAE;AAC5E,eAAK,KAAK,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,QAC9E;AAAA,MACF;AAAA,IACF;AAGA,SAAK,eAAe;AACpB,SAAK,kBAAkB;AACvB,SAAK,YAAY;AACjB,SAAK,YAAY;AAEjB,SAAK,gBAAgB,CAAC,SAAc;AAClC,YAAM,OAAe,OAAO,SAAS,IAAI,IAAI,OAAO,MAAM;AAC1D,YAAM,aAAsB,OAAO,SAAS,IAAI,IAAI,yBAAyB,IAAI,IAAI,QAAQ,MAAM,UAAU;AAC7G,UAAI,CAAC,OAAO,SAAS,IAAI,EAAG;AAI5B,UAAI,CAAC,OAAO,SAAS,IAAI,GAAG;AAC1B,aAAK,kBAAkB;AAAA,MACzB,WAAW,KAAK,iBAAiB;AAC/B;AAAA,MACF;AAGA,YAAM,OAAO,gBAAgB,IAAI;AACjC,iBAAW,OAAO,MAAM;AACtB,cAAM,IAAI,YAAY,GAAG;AACzB,YAAI,MAAM,EAAG,MAAK,YAAY;AAC9B,YAAI,MAAM,EAAG,MAAK,YAAY;AAAA,MAChC;AAGA,UAAI,CAAC,KAAK,cAAc;AACtB,YAAI,CAAC,WAAY;AACjB,aAAK,eAAe;AACpB,aAAK,OAAO,KAAK,0EAA0E;AAAA,MAC7F;AAGA,UAAI,cAAc,KAAK,aAAa,KAAK,WAAW;AAElD,YAAI,SAAS;AACb,YAAI,SAAS;AACb,mBAAW,OAAO,MAAM;AACtB,gBAAM,IAAI,YAAY,GAAG;AACzB,cAAI,MAAM,EAAG,UAAS;AACtB,cAAI,MAAM,EAAG,UAAS;AAAA,QACxB;AACA,YAAI,CAAC,UAAU,CAAC,QAAQ;AACtB,gBAAM,UAAU,OAAO,OAAO;AAAA,YAC5B;AAAA,YAAmB,KAAK;AAAA,YACxB;AAAA,YAAmB,KAAK;AAAA,YACxB;AAAA,UACF,CAAC;AACD,wBAAc,OAAO;AACrB;AAAA,QACF;AAAA,MACF;AAEA,oBAAc,IAAI;AAAA,IACpB;AAGA,SAAK,YAAY,GAAG,mBAA0B,KAAK,aAAoB;AACvE,SAAK,YAAY,GAAG,cAAc,KAAK,aAAoB;AAG3D,WAAO,OAAO,GAAG,QAAQ,CAAC,SAAiB;AAEzC,iBAAW,UAAU,KAAK,SAAS;AACjC,YAAI,CAAC,OAAO,WAAW;AACrB,cAAI;AACF,mBAAO,MAAM,IAAI;AAAA,UACnB,SAAS,OAAO;AAEd,iBAAK,QAAQ,OAAO,MAAM;AAAA,UAC5B;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAGD,QAAI,eAAe;AACnB,WAAO,OAAO,GAAG,QAAQ,CAAC,SAAS;AACjC,YAAM,SAAS,KAAK,SAAS;AAC7B,sBAAgB;AAIhB,YAAM,kBACJ,OAAO,SAAS,uBAAuB,KACvC,OAAO,SAAS,sBAAsB,KACtC,OAAO,SAAS,2BAA2B,KAC3C,OAAO,SAAS,UAAU,KAC1B,OAAO,SAAS,YAAY,KAC5B,OAAO,SAAS,wBAAwB,KACxC,OAAO,SAAS,0BAA0B;AAE5C,UAAI,iBAAiB;AAEnB,aAAK,OAAO,KAAK,qDAAqD,OAAO,KAAK,CAAC,EAAE;AACrF;AAAA,MACF;AAGA,YAAM,kBACJ,OAAO,SAAS,oBAAoB,KACpC,OAAO,SAAS,eAAe,KAC/B,OAAO,SAAS,wBAAwB,KACxC,OAAO,SAAS,aAAa,KAC7B,OAAO,SAAS,oBAAoB,KACpC,OAAO,SAAS,gBAAgB,KAChC,OAAO,SAAS,mBAAmB;AAErC,UAAI,iBAAiB;AACnB,aAAK,OAAO,MAAM,qDAAqD,OAAO,KAAK,CAAC,EAAE;AAEtF,aAAK,KAAK,SAAS,IAAI,MAAM,iBAAiB,MAAM,EAAE,CAAC;AAAA,MACzD,OAAO;AACL,aAAK,OAAO,KAAK,6CAA6C,OAAO,KAAK,CAAC,EAAE;AAAA,MAC/E;AAAA,IACF,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,SAAS;AAC3B,UAAI,SAAS,GAAG;AACd,aAAK,OAAO,MAAM,sDAAsD,IAAI,EAAE;AAC9E,aAAK,KAAK,SAAS,IAAI,MAAM,2BAA2B,IAAI,EAAE,CAAC;AAAA,MACjE;AACA,WAAK,SAAS;AACd,WAAK,KAAK,OAAO;AAAA,IACnB,CAAC;AAED,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACrB,WAAO,oBAAoB,KAAK,UAAU,GAAG,KAAK,IAAI;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAK1B,eAAW,UAAU,KAAK,SAAS;AACjC,UAAI,CAAC,OAAO,WAAW;AACrB,eAAO,IAAI;AAAA,MACb;AAAA,IACF;AACA,SAAK,QAAQ,MAAM;AAGnB,QAAI;AACF,YAAM,KAAK,YAAY,KAAK;AAAA,IAC9B,QAAQ;AAAA,IAER;AAGA,QAAI,KAAK,eAAe;AACtB,WAAK,YAAY,eAAe,mBAA0B,KAAK,aAAoB;AACnF,WAAK,YAAY,eAAe,cAAc,KAAK,aAAoB;AAAA,IACzE;AACA,SAAK,gBAAgB;AAGrB,QAAI,KAAK,eAAe;AACtB,YAAM,OAAO,KAAK;AAClB,UAAI;AACF,aAAK,KAAK,SAAS;AAAA,MACrB,QAAQ;AAAA,MAER;AAEA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,IAAI,WAAW,MAAM;AACzB,cAAI;AACF,iBAAK,KAAK,SAAS;AAAA,UACrB,QAAQ;AAAA,UAER;AACA,kBAAQ;AAAA,QACV,GAAG,IAAI;AAEP,QAAC,GAAW,QAAQ;AACpB,aAAK,KAAK,SAAS,MAAM;AACvB,uBAAa,CAAC;AACd,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AACA,SAAK,gBAAgB;AAGrB,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI,QAAc,CAAC,YAAY;AAEnC,QAAC,KAAK,YAAoB,sBAAsB;AAChD,QAAC,KAAK,YAAoB,uBAAuB;AACjD,aAAK,WAAY,MAAM,MAAM,QAAQ,CAAC;AAAA,MACxC,CAAC;AACD,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AACF;;;ACraA,SAAS,gBAAAE,qBAAoB;AAC7B,YAAYC,WAAU;;;ACDtB,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,SAAAC,cAAkD;AA8B3D,IAAM,WAAW,OAAO,KAAK,CAAC,KAAM,GAAI,CAAC;AACzC,IAAM,WAAW,OAAO,KAAK,CAAC,KAAM,GAAI,CAAC;AAUlC,IAAM,mBAAN,cAA+BD,cAAa;AAAA,EAChC;AAAA,EAIT,SAAgD;AAAA,EAChD,UAAU;AAAA,EACV,SAAS;AAAA,EACT,aAAa,OAAO,MAAM,CAAC;AAAA,EAC3B,aAAa;AAAA,EACb,gBAAgB;AAAA,EAExB,YAAY,SAAkC;AAC5C,UAAM;AACN,SAAK,UAAU;AAAA,MACb,OAAO,QAAQ;AAAA,MACf,SAAS,QAAQ,WAAW;AAAA,MAC5B,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,WAAW,KAAK,OAAQ;AACjC,SAAK,UAAU;AAEf,UAAM,EAAE,OAAO,SAAS,OAAO,QAAQ,OAAO,IAAI,KAAK;AAGvD,UAAM,OAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA,UAAU,SAAS,SAAS;AAAA,MAC5B;AAAA,MACA;AAAA,IACF;AAGA,UAAM,UAAoB,CAAC;AAG3B,QAAI,SAAS,QAAQ;AACnB,YAAM,IAAI,SAAS;AACnB,YAAM,IAAI,UAAU;AACpB,cAAQ,KAAK,SAAS,CAAC,IAAI,CAAC,EAAE;AAAA,IAChC;AAGA,QAAI,QAAQ;AACV,cAAQ,KAAK,OAAO,MAAM,EAAE;AAAA,IAC9B;AAEA,QAAI,QAAQ,SAAS,GAAG;AACtB,WAAK,KAAK,OAAO,QAAQ,KAAK,GAAG,CAAC;AAAA,IACpC;AAGA,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,OAAO;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,SAAK,IAAI,SAAS,8BAA8B,KAAK,KAAK,GAAG,CAAC,EAAE;AAEhE,SAAK,SAASC,OAAM,UAAU,MAAM;AAAA,MAClC,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,SAAK,OAAO,OAAO,GAAG,QAAQ,CAAC,SAAiB;AAC9C,WAAK,eAAe,IAAI;AAAA,IAC1B,CAAC;AAED,SAAK,OAAO,OAAO,GAAG,QAAQ,CAAC,SAAiB;AAC9C,YAAM,MAAM,KAAK,SAAS,EAAE,KAAK;AACjC,UAAI,KAAK;AACP,aAAK,IAAI,SAAS,WAAW,GAAG,EAAE;AAAA,MACpC;AAAA,IACF,CAAC;AAED,SAAK,OAAO,GAAG,SAAS,CAAC,SAAS;AAChC,WAAK,IAAI,SAAS,2BAA2B,IAAI,EAAE;AACnD,WAAK,SAAS;AACd,UAAI,CAAC,KAAK,QAAQ;AAChB,aAAK,KAAK,SAAS,IAAI;AAAA,MACzB;AAAA,IACF,CAAC;AAED,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,IAAI,SAAS,iBAAiB,IAAI,OAAO,EAAE;AAChD,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,YAAoB,WAA0B;AACjD,QAAI,CAAC,KAAK,WAAW,KAAK,UAAU,CAAC,KAAK,QAAQ;AAChD;AAAA,IACF;AAEA,SAAK,gBAAgB,aAAa,KAAK,IAAI,IAAI;AAE/C,QAAI;AACF,WAAK,OAAO,MAAM,MAAM,UAAU;AAAA,IACpC,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,8BAA8B,GAAG,EAAE;AAAA,IACvD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,eAAe,MAAoB;AACzC,SAAK,aAAa,OAAO,OAAO,CAAC,KAAK,YAAY,IAAI,CAAC;AAGvD,WAAO,MAAM;AAEX,YAAM,WAAW,KAAK,WAAW,QAAQ,QAAQ;AACjD,UAAI,WAAW,GAAG;AAEhB,aAAK,aAAa,OAAO,MAAM,CAAC;AAChC;AAAA,MACF;AAGA,UAAI,WAAW,GAAG;AAChB,aAAK,aAAa,KAAK,WAAW,SAAS,QAAQ;AAAA,MACrD;AAGA,YAAM,WAAW,KAAK,WAAW,QAAQ,UAAU,CAAC;AACpD,UAAI,WAAW,GAAG;AAEhB;AAAA,MACF;AAGA,YAAM,WAAW,WAAW;AAC5B,YAAM,YAAY,KAAK,WAAW,SAAS,GAAG,QAAQ;AAGtD,WAAK,aAAa,KAAK,WAAW,SAAS,QAAQ;AAGnD,WAAK;AACL,YAAM,QAAoB;AAAA,QACxB,MAAM;AAAA,QACN,WAAW,KAAK;AAAA,MAClB;AACA,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AAEd,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,aAAK,OAAO,MAAM,IAAI;AAAA,MACxB,QAAQ;AAAA,MAER;AAGA,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,KAAK,KAAK;AAChB,YAAI,CAAC,IAAI;AACP,kBAAQ;AACR;AAAA,QACF;AAEA,cAAM,UAAU,WAAW,MAAM;AAC/B,aAAG,KAAK,SAAS;AACjB,kBAAQ;AAAA,QACV,GAAG,GAAI;AAEP,WAAG,KAAK,SAAS,MAAM;AACrB,uBAAa,OAAO;AACpB,kBAAQ;AAAA,QACV,CAAC;AAED,YAAI;AACF,aAAG,KAAK,SAAS;AAAA,QACnB,QAAQ;AACN,uBAAa,OAAO;AACpB,kBAAQ;AAAA,QACV;AAAA,MACF,CAAC;AAED,WAAK,SAAS;AAAA,IAChB;AAEA,SAAK,KAAK,SAAS,CAAC;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,KAAK,WAAW,CAAC,KAAK,UAAU,KAAK,WAAW;AAAA,EACzD;AAAA,EAEQ,IACN,OACA,SACM;AACN,SAAK,QAAQ,SAAS,OAAO,sBAAsB,OAAO,EAAE;AAAA,EAC9D;AACF;AAmBO,SAAS,sBAA8B;AAC5C,SAAO,gBAAgB,KAAK,IAAI,CAAC;AACnC;AAEO,SAAS,oBAAoB,UAA0B;AAC5D,SAAO,uCAAuC,QAAQ;AACxD;AAEO,SAAS,iBAAiB,OAAe,UAA0B;AACxE,QAAM,SAAS,OAAO;AAAA,IACpB,KAAK,QAAQ;AAAA;AAAA,kBAAmD,MAAM,MAAM;AAAA;AAAA;AAAA,EAC9E;AACA,SAAO,OAAO,OAAO,CAAC,QAAQ,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC;AAC3D;;;ADtPO,IAAM,sBAAN,cAAkCC,cAAa;AAAA,EACnC;AAAA,EACA,UAAU,oBAAI,IAAyB;AAAA,EAChD,aAAiC;AAAA,EACjC,cAAuC;AAAA,EACvC,eAA0D;AAAA,EAC1D,aAAmC;AAAA,EACnC,gBAAwC;AAAA,EACxC,UAAU;AAAA,EACV,kBAAkB;AAAA,EAE1B,YAAY,SAAqC;AAC/C,UAAM;AACN,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AAEf,UAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,UAAM,OAAO,KAAK,QAAQ,QAAQ;AAClC,UAAMC,QAAO,KAAK,QAAQ,QAAQ;AAElC,SAAK,aAAkB,mBAAa,CAAC,KAAK,QAAQ;AAChD,WAAK,cAAc,KAAK,KAAKA,KAAI;AAAA,IACnC,CAAC;AAED,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAK,WAAY,GAAG,SAAS,CAAC,QAAQ;AACpC,aAAK,IAAI,SAAS,sBAAsB,IAAI,OAAO,EAAE;AACrD,eAAO,GAAG;AAAA,MACZ,CAAC;AAED,WAAK,WAAY,OAAO,MAAM,MAAM,MAAM;AACxC,aAAK;AAAA,UACH;AAAA,UACA,kCAAkC,IAAI,IAAI,IAAI,GAAGA,KAAI;AAAA,QACvD;AACA,aAAK,KAAK,WAAW,EAAE,MAAM,MAAM,MAAAA,MAAK,CAAC;AACzC,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAS;AACnB,SAAK,UAAU;AAGf,eAAW,CAAC,IAAI,MAAM,KAAK,KAAK,SAAS;AACvC,UAAI;AACF,eAAO,SAAS,IAAI;AAAA,MACtB,QAAQ;AAAA,MAER;AACA,WAAK,QAAQ,OAAO,EAAE;AAAA,IACxB;AAGA,UAAM,KAAK,WAAW;AAGtB,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAK,WAAY,MAAM,MAAM,QAAQ,CAAC;AAAA,MACxC,CAAC;AACD,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,IAAI,QAAQ,sBAAsB;AACvC,SAAK,KAAK,SAAS;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKQ,cACN,KACA,KACA,cACM;AACN,UAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,IAAI,EAAE;AAEhE,QAAI,IAAI,aAAa,cAAc;AACjC,UAAI,aAAa;AACjB,UAAI,IAAI,WAAW;AACnB;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,OAAO;AACxB,UAAI,aAAa;AACjB,UAAI,IAAI,oBAAoB;AAC5B;AAAA,IACF;AAEA,SAAK,kBAAkB,KAAK,GAAG;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKQ,kBACN,KACA,KACM;AACN,UAAM,WAAW,UAAU,EAAE,KAAK,eAAe;AACjD,UAAM,WAAW,oBAAoB;AAErC,UAAM,SAAsB;AAAA,MAC1B,IAAI;AAAA,MACJ,UAAU;AAAA,MACV;AAAA,MACA,aAAa,KAAK,IAAI;AAAA,IACxB;AAEA,SAAK,QAAQ,IAAI,UAAU,MAAM;AACjC,SAAK;AAAA,MACH;AAAA,MACA,2BAA2B,QAAQ,YAAY,KAAK,QAAQ,IAAI;AAAA,IAClE;AACA,SAAK,KAAK,oBAAoB,EAAE,IAAI,UAAU,OAAO,KAAK,QAAQ,KAAK,CAAC;AAGxE,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB,oBAAoB,QAAQ;AAAA,MAC5C,iBAAiB;AAAA,MACjB,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY;AAAA,IACd,CAAC;AAGD,UAAM,UAAU,MAAM;AACpB,WAAK,QAAQ,OAAO,QAAQ;AAC5B,WAAK;AAAA,QACH;AAAA,QACA,8BAA8B,QAAQ,gBAAgB,KAAK,QAAQ,IAAI;AAAA,MACzE;AACA,WAAK,KAAK,uBAAuB;AAAA,QAC/B,IAAI;AAAA,QACJ,OAAO,KAAK,QAAQ;AAAA,MACtB,CAAC;AAGD,UAAI,KAAK,QAAQ,SAAS,GAAG;AAC3B,aAAK,WAAW;AAAA,MAClB;AAAA,IACF;AAEA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AAGvB,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,YAAY;AAAA,IACnB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAA6B;AACzC,QAAI,KAAK,YAAa;AAEtB,SAAK,IAAI,QAAQ,iCAAiC;AAElD,UAAM,EAAE,KAAK,SAAS,SAAS,SAAS,SAAS,OAAO,QAAQ,OAAO,IACrE,KAAK;AAEP,QAAI;AAGF,WAAK,eAAe;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,EAAE,QAAQ,IAAI;AAAA,MAC1B;AAGA,WAAK,aAAa,KAAK,WAAW;AAAA,IACpC,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,2BAA2B,GAAG,EAAE;AAClD,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAA4B;AACxC,QAAI,CAAC,KAAK,aAAc;AAExB,QAAI,cAAwB,CAAC;AAC7B,QAAI,qBAAqB;AAEzB,QAAI;AACF,uBAAiB,SAAS,KAAK,cAAc;AAC3C,YAAI,CAAC,KAAK,WAAW,KAAK,QAAQ,SAAS,EAAG;AAG9C,cAAM,EAAE,MAAM,MAAM,cAAc,UAAU,IAAI;AAEhD,YAAI,SAAS,YAAY,SAAS,SAAU;AAC5C,YAAI,CAAC,QAAQ,KAAK,WAAW,EAAG;AAGhC,YAAI;AACJ,YAAI,cAAc,QAAQ;AACxB,mBAASC,iBAAoB,IAAI;AACjC,cAAI,CAAC,KAAK,eAAe;AACvB,iBAAK,gBAAgB;AACrB,iBAAK,gBAAgB;AAAA,UACvB;AAAA,QACF,OAAO;AACL,mBAAS,gBAAoB,IAAI;AACjC,cAAI,CAAC,KAAK,eAAe;AACvB,iBAAK,gBAAgB;AACrB,iBAAK,gBAAgB;AAAA,UACvB;AAAA,QACF;AAGA,YAAI,oBAAoB;AACtB,cAAI,SAAS,UAAU;AACrB,iCAAqB;AAAA,UACvB,OAAO;AACL;AAAA,UACF;AAAA,QACF;AAGA,YAAI,KAAK,aAAa;AACpB,eAAK,YAAY,KAAK,QAAQ,YAAY;AAAA,QAC5C;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,KAAK,SAAS;AAChB,aAAK,IAAI,SAAS,iBAAiB,GAAG,EAAE;AACxC,aAAK,KAAK,SAAS,GAAG;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAwB;AAC9B,QAAI,KAAK,eAAe,CAAC,KAAK,cAAe;AAE7C,UAAM,EAAE,SAAS,OAAO,QAAQ,OAAO,IAAI,KAAK;AAEhD,SAAK,cAAc,IAAI,iBAAiB;AAAA,MACtC,OAAO,KAAK;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,KAAK,QAAQ;AAAA,IACvB,CAAC;AAED,SAAK,YAAY,GAAG,SAAS,CAAC,UAAsB;AAClD,WAAK,eAAe,KAAK;AAAA,IAC3B,CAAC;AAED,SAAK,YAAY,GAAG,SAAS,CAAC,QAAQ;AACpC,WAAK,IAAI,SAAS,sBAAsB,GAAG,EAAE;AAAA,IAC/C,CAAC;AAED,SAAK,YAAY,GAAG,SAAS,MAAM;AACjC,WAAK,IAAI,SAAS,oBAAoB;AAAA,IACxC,CAAC;AAED,SAAK,YAAY,MAAM;AACvB,SAAK;AAAA,MACH;AAAA,MACA,qCAAqC,KAAK,aAAa;AAAA,IACzD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,OAAyB;AAC9C,eAAW,UAAU,KAAK,QAAQ,OAAO,GAAG;AAC1C,UAAI;AACF,cAAM,YAAY,iBAAiB,MAAM,MAAM,OAAO,QAAQ;AAC9D,eAAO,SAAS,MAAM,SAAS;AAAA,MACjC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAA4B;AAExC,QAAI,KAAK,aAAa;AACpB,YAAM,KAAK,YAAY,KAAK;AAC5B,WAAK,cAAc;AAAA,IACrB;AAGA,QAAI,KAAK,cAAc;AACrB,UAAI;AACF,cAAM,KAAK,aAAa,OAAO,MAAgB;AAAA,MACjD,QAAQ;AAAA,MAER;AACA,WAAK,eAAe;AAAA,IACtB;AAGA,QAAI,KAAK,YAAY;AACnB,UAAI;AACF,cAAM,KAAK;AAAA,MACb,QAAQ;AAAA,MAER;AACA,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,gBAAgB;AAErB,SAAK,IAAI,SAAS,gBAAgB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAyB;AACvB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,YAKE;AACA,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,SAAS,KAAK,QAAQ;AAAA,MACtB,OAAO,KAAK;AAAA,MACZ,QAAQ,KAAK,aAAa,cAAc,KAAK;AAAA,IAC/C;AAAA,EACF;AAAA,EAEQ,IACN,OACA,SACM;AACN,SAAK,QAAQ,SAAS,OAAO,yBAAyB,OAAO,EAAE;AAAA,EACjE;AACF;;;AErZA,SAAS,gBAAAC,qBAAoB;;;ACP7B,SAAuB,SAAAC,cAAa;AACpC,SAAS,oBAA4B;AACrC,SAAS,gBAAAC,qBAAoB;AAgCtB,IAAM,sBAAN,cAAkCA,cAAqB;AAAA,EAC3C;AAAA,EAET,SAAwB;AAAA,EACxB,SAA8B;AAAA,EAC9B,OAAO;AAAA,EACP,WAAiC;AAAA,EACjC,UAAU;AAAA,EAElB,YAAY,UAAsC,CAAC,GAAG;AACpD,UAAM;AACN,SAAK,OAAO;AAAA,MACV,YAAY,QAAQ,cAAc;AAAA,MAClC,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,cAAc,QAAQ,gBAAgB;AAAA,MACtC,aAAa,QAAQ,eAAe;AAAA,MACpC,aAAa,QAAQ,eAAe;AAAA,MACpC,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACnE;AAAA,EACF;AAAA,EAEQ,IAAI,OAA4C,SAAuB;AAC7E,SAAK,KAAK,SAAS,OAAO,yBAAyB,OAAO,EAAE;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAuB;AAC3B,QAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,SAAK,WAAW,KAAK,OAAO;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,SAAwB;AACpC,QAAI,KAAK,QAAS,OAAM,IAAI,MAAM,oBAAoB;AAGtD,SAAK,SAAS,aAAa,MAAM;AACjC,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAK,OAAQ,KAAK,SAAS,MAAM;AACjC,WAAK,OAAQ,KAAK,EAAE,SAAS,aAAa,MAAM,EAAE,GAAG,MAAM;AACzD,aAAK,OAAQ,eAAe,SAAS,MAAM;AAC3C,aAAK,OAAQ,KAAK,OAAQ,QAAQ,EAAuB;AACzD,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AACD,SAAK,IAAI,QAAQ,mCAAmC,KAAK,IAAI,EAAE;AAE/D,SAAK,OAAO,GAAG,WAAW,CAAC,cAAc,KAAK,UAAU,SAAS,CAAC;AAClE,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,IAAI,SAAS,iBAAiB,IAAI,OAAO,EAAE;AAChD,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB,CAAC;AAID,UAAM,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,KAAK,KAAK,cAAc;AAAA,MAC/B;AAAA,MACA,OAAO,KAAK,KAAK,YAAY;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,KAAK,KAAK,WAAW;AAAA,MAC5B;AAAA,MACA,OAAO,KAAK,KAAK,WAAW;AAAA;AAAA;AAAA,MAG5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,mBAAmB,KAAK,IAAI;AAAA,IAC9B;AACA,SAAK,IAAI,QAAQ,yBAAyB,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,GAAG,CAAC,EAAE;AAElF,SAAK,SAASD,OAAM,KAAK,KAAK,YAAY,MAAM;AAAA,MAC9C,OAAO,CAAC,QAAQ,UAAU,MAAM;AAAA,IAClC,CAAC;AAED,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,IAAI,SAAS,uBAAuB,IAAI,OAAO,EAAE;AACtD,WAAK,KAAK,SAAS,GAAG;AAAA,IACxB,CAAC;AACD,SAAK,OAAO,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAEhD,WAAK,IAAI,SAAS,kBAAkB,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE;AAAA,IAC/D,CAAC;AACD,SAAK,OAAO,GAAG,QAAQ,CAAC,SAAS;AAC/B,WAAK,IAAI,SAAS,IAAI,SAAS,QAAQ,sBAAsB,IAAI,EAAE;AACnE,WAAK,KAAK,QAAQ,IAAI;AAAA,IACxB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQ,KAAsB;AAC5B,QAAI,KAAK,QAAS,QAAO;AACzB,QAAI,CAAC,KAAK,QAAQ,SAAS,CAAC,KAAK,OAAO,MAAM,UAAU;AACtD,WAAK,IAAI,SAAS,iDAA4C;AAC9D,aAAO;AAAA,IACT;AACA,WAAO,KAAK,OAAO,MAAM,MAAM,GAAG;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,QAAI;AACF,WAAK,QAAQ,OAAO,IAAI;AAAA,IAC1B,QAAQ;AAAA,IAER;AACA,QAAI,KAAK,UAAU,KAAK,OAAO,aAAa,MAAM;AAEhD,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,IAAI,WAAW,MAAM;AACzB,eAAK,QAAQ,KAAK,SAAS;AAC3B,kBAAQ;AAAA,QACV,GAAG,GAAG;AACN,aAAK,QAAQ,KAAK,QAAQ,MAAM;AAC9B,uBAAa,CAAC;AACd,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AACA,SAAK,SAAS;AACd,QAAI,KAAK,QAAQ;AACf,UAAI;AAAE,aAAK,OAAO,MAAM;AAAA,MAAG,QAAQ;AAAA,MAAe;AAClD,WAAK,SAAS;AAAA,IAChB;AACA,SAAK,IAAI,QAAQ,SAAS;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBQ,UAAU,QAAsB;AACtC,QAAI,OAAO,SAAS,GAAI;AACxB,UAAM,KAAK,OAAO,CAAC;AACnB,UAAM,KAAK,OAAO,CAAC;AACnB,UAAM,UAAU,MAAM;AACtB,QAAI,YAAY,EAAG;AACnB,UAAM,WAAW,KAAK,QAAU;AAChC,UAAM,aAAa,KAAK,QAAU;AAClC,UAAM,YAAY,KAAK;AACvB,UAAM,UAAU,KAAK,SAAU;AAC/B,QAAI,SAAS,KAAK,YAAY;AAC9B,QAAI,WAAW;AACb,UAAI,OAAO,SAAS,SAAS,EAAG;AAChC,YAAM,SAAS,OAAO,aAAa,SAAS,CAAC;AAC7C,gBAAU,IAAI,SAAS;AAAA,IACzB;AACA,QAAI,MAAM,OAAO;AACjB,QAAI,SAAS;AACX,YAAM,MAAM,OAAO,OAAO,SAAS,CAAC;AACpC,aAAO;AAAA,IACT;AACA,QAAI,UAAU,IAAK;AAEnB,UAAM,YAAY,OAAO,aAAa,CAAC;AACvC,UAAM,UAAU,OAAO,KAAK,OAAO,SAAS,QAAQ,GAAG,CAAC;AACxD,SAAK,KAAK,UAAU,EAAE,SAAS,WAAW,OAAO,CAAC;AAAA,EACpD;AACF;;;AD9FA,SAAS,oBAAoB,QAA0B;AACrD,QAAM,WAAqB,CAAC;AAC5B,MAAI,SAAS;AAEb,SAAO,SAAS,OAAO,QAAQ;AAE7B,QAAI,eAAe;AACnB,QACE,SAAS,KAAK,OAAO,UACrB,OAAO,MAAM,MAAM,KACnB,OAAO,SAAS,CAAC,MAAM,KACvB,OAAO,SAAS,CAAC,MAAM,KACvB,OAAO,SAAS,CAAC,MAAM,GACvB;AACA,qBAAe;AAAA,IACjB,WACE,SAAS,KAAK,OAAO,UACrB,OAAO,MAAM,MAAM,KACnB,OAAO,SAAS,CAAC,MAAM,KACvB,OAAO,SAAS,CAAC,MAAM,GACvB;AACA,qBAAe;AAAA,IACjB,OAAO;AACL;AACA;AAAA,IACF;AAEA,UAAM,YAAY,SAAS;AAG3B,QAAI,UAAU,OAAO;AACrB,aAAS,IAAI,WAAW,IAAI,OAAO,SAAS,GAAG,KAAK;AAClD,UACE,OAAO,CAAC,MAAM,KACd,OAAO,IAAI,CAAC,MAAM,MACjB,OAAO,IAAI,CAAC,MAAM,KAChB,IAAI,IAAI,OAAO,UAAU,OAAO,IAAI,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,MAAM,IACrE;AACA,kBAAU;AACV;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,WAAW;AACvB,eAAS,KAAK,OAAO,SAAS,WAAW,OAAO,CAAC;AAAA,IACnD;AAEA,aAAS;AAAA,EACX;AAEA,SAAO;AACT;AAKA,SAAS,eAAe,SAAyB;AAC/C,SAAO,QAAQ,CAAC,IAAK;AACvB;AAKA,SAASE,gBAAe,SAAyB;AAC/C,SAAQ,QAAQ,CAAC,KAAM,IAAK;AAC9B;AAiBO,IAAM,uBAAN,cAAmCC,cAAa;AAAA,EACpC;AAAA,EACA,WAAW,oBAAI,IAA2B;AAAA,EACnD,mBAAmB;AAAA,EACnB,eAAoB;AAAA,EAE5B,YAAY,SAAsC;AAChD,UAAM;AACN,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,aAA2B;AACvC,QAAI,KAAK,aAAc,QAAO,KAAK;AAEnC,QAAI;AACF,WAAK,eAAe,MAAM,OAAO,QAAQ;AACzC,aAAO,KAAK;AAAA,IACd,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR;AAAA,SAAsF,GAAG;AAAA,MAC3F;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,gBAAoE;AACxE,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,UAAM,EAAE,mBAAmB,kBAAkB,sBAAsB,IACjE;AAEF,UAAM,YAAY,UAAU,EAAE,KAAK,gBAAgB,IAAI,KAAK,IAAI,CAAC;AAEjE,SAAK,IAAI,QAAQ,2BAA2B,SAAS,EAAE;AAGvD,UAAM,aAAoB,CAAC;AAG3B,UAAM,cAAc,KAAK,QAAQ,eAAe;AAAA,MAC9C;AAAA,IACF;AACA,eAAW,QAAQ,aAAa;AAC9B,iBAAW,KAAK,EAAE,KAAK,CAAC;AAAA,IAC1B;AAGA,QAAI,KAAK,QAAQ,aAAa;AAC5B,iBAAW,KAAK,GAAG,KAAK,QAAQ,WAAW;AAAA,IAC7C;AAGA,UAAM,iBAAiB,IAAI,kBAAkB;AAAA,MAC3C;AAAA,MACA,cAAc,KAAK,QAAQ;AAAA,MAC3B,4BAA4B,KAAK,QAAQ;AAAA,MACzC,oBAAoB,KAAK,QAAQ;AAAA,MACjC,QAAQ;AAAA,QACN,OAAO;AAAA,UACL,IAAI,sBAAsB;AAAA,YACxB,UAAU;AAAA,YACV,WAAW;AAAA,YACX,cAAc;AAAA,cACZ,EAAE,MAAM,OAAO;AAAA,cACf,EAAE,MAAM,QAAQ,WAAW,MAAM;AAAA,cACjC,EAAE,MAAM,YAAY;AAAA,YACtB;AAAA,YACA,YACE;AAAA,UACJ,CAAC;AAAA,QACH;AAAA,QACA,OAAO;AAAA,UACL,IAAI,sBAAsB;AAAA,YACxB,UAAU;AAAA,YACV,WAAW;AAAA,YACX,UAAU;AAAA,UACZ,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC;AAGD,UAAM,UAAyB;AAAA,MAC7B,IAAI;AAAA,MACJ;AAAA,MACA,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,cAAc;AAAA,MACd,UAAU;AAAA,MACV,aAAa;AAAA,MACb,YAAY;AAAA,MACZ,WAAW,oBAAI,KAAK;AAAA,MACpB,OAAO;AAAA,MACP,OAAO;AAAA,QACL,aAAa;AAAA,QACb,aAAa;AAAA,QACb,WAAW;AAAA,QACX,mBAAmB;AAAA,MACrB;AAAA,IACF;AAEA,SAAK,SAAS,IAAI,WAAW,OAAO;AAIpC,UAAM,YAAa,KAAK,OAAO,IAAI,eAAgB;AACnD,UAAM,aAAa,IAAI,iBAAiB,EAAE,MAAM,SAAS,MAAM,UAAU,CAAC;AAC1E,UAAM,cAAc,eAAe,SAAS,UAAU;AACtD,YAAQ,aAAa;AAGrB,SAAK;AAAA,MACH;AAAA,MACA,6BAA6B,WAAW,IAAI,mBAAmB,KAAK,UAAU,aAAa,gBAAgB,KAAK,CAAC,CAAC,CAAC;AAAA,IACrH;AAGA,UAAM,YAAa,KAAK,OAAO,IAAI,eAAgB;AACnD,UAAM,aAAa,IAAI,iBAAiB,EAAE,MAAM,SAAS,MAAM,UAAU,CAAC;AAC1E,mBAAe,SAAS,UAAU;AAClC,YAAQ,aAAa;AAGrB,UAAM,mBAAmB,eAAe,kBAAkB,SAAS;AAAA,MACjE,SAAS;AAAA,MACT,gBAAgB;AAAA;AAAA,IAClB,CAAC;AACD,YAAQ,mBAAmB;AAE3B,qBAAiB,SAAS,MAAM;AAC9B,WAAK,IAAI,QAAQ,yCAAyC,SAAS,EAAE;AAAA,IACvE;AAGA,QAAI,KAAK,QAAQ,gBAAgB;AAC/B,YAAM,cAAc,eAAe,kBAAkB,YAAY;AAAA,QAC/D,SAAS;AAAA,MACX,CAAC;AACD,cAAQ,cAAc;AAEtB,kBAAY,SAAS,MAAM;AACzB,aAAK;AAAA,UACH;AAAA,UACA,4CAA4C,SAAS;AAAA,QACvD;AACA,aAAK,KAAK,oBAAoB,EAAE,UAAU,CAAC;AAAA,MAC7C;AAEA,kBAAY,YAAY,OAAO,UAAe;AAE5C,YAAI,QAAQ,YAAY,MAAM,gBAAgB,aAAa;AACzD,cAAI;AACF,kBAAM,YAAY,OAAO,KAAK,MAAM,IAAI;AACxC,kBAAM,QAAQ,SAAS,UAAU,SAAS;AAC1C,oBAAQ,MAAM,qBAAqB,UAAU;AAAA,UAC/C,SAAS,KAAK;AACZ,iBAAK,IAAI,SAAS,kCAAkC,GAAG,EAAE;AAAA,UAC3D;AAAA,QACF;AAAA,MACF;AAEA,kBAAY,UAAU,MAAM;AAC1B,aAAK;AAAA,UACH;AAAA,UACA,4CAA4C,SAAS;AAAA,QACvD;AACA,aAAK,KAAK,oBAAoB,EAAE,UAAU,CAAC;AAAA,MAC7C;AAAA,IACF;AAGA,mBAAe,yBAAyB,UAAU,CAAC,UAAkB;AACnE,WAAK,IAAI,QAAQ,4BAA4B,SAAS,KAAK,KAAK,EAAE;AAClE,UAAI,UAAU,aAAa;AACzB,gBAAQ,QAAQ;AAChB,aAAK,KAAK,qBAAqB,EAAE,UAAU,CAAC;AAAA,MAC9C,WAAW,UAAU,UAAU;AAC7B,gBAAQ,QAAQ;AAChB,aAAK,aAAa,SAAS,EAAE,MAAM,CAAC,QAAQ;AAC1C,eAAK,IAAI,SAAS,gCAAgC,KAAK,KAAK,GAAG,EAAE;AAAA,QACnE,CAAC;AAAA,MACH;AAAA,IAEF,CAAC;AAGD,mBAAe,sBAAsB,UAAU,CAAC,UAAkB;AAChE,WAAK,IAAI,SAAS,wBAAwB,SAAS,KAAK,KAAK,EAAE;AAC/D,UAAI,UAAU,YAAY,UAAU,UAAU;AAC5C,aAAK,aAAa,SAAS,EAAE,MAAM,CAAC,QAAQ;AAC1C,eAAK;AAAA,YACH;AAAA,YACA,uCAAuC,KAAK,KAAK,GAAG;AAAA,UACtD;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAGD,UAAM,QAAQ,MAAM,eAAe,YAAY;AAC/C,UAAM,eAAe,oBAAoB,KAAK;AAG9C,UAAM,KAAK,oBAAoB,gBAAgB,GAAI;AAEnD,UAAM,mBAAmB,eAAe;AACxC,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AAEA,SAAK,KAAK,mBAAmB,EAAE,UAAU,CAAC;AAE1C,WAAO;AAAA,MACL;AAAA,MACA,OAAO;AAAA,QACL,KAAK,iBAAiB;AAAA,QACtB,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,WAAmB,QAAqC;AACzE,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,WAAW,SAAS,YAAY;AAAA,IAClD;AAEA,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,UAAM,EAAE,sBAAsB,IAAI;AAElC,SAAK,IAAI,QAAQ,sCAAsC,SAAS,EAAE;AAGlE,UAAM,QAAQ,eAAe;AAAA,MAC3B,IAAI,sBAAsB,OAAO,KAAK,OAAO,IAAI;AAAA,IACnD;AAGA,UAAM,KAAK,kBAAkB,OAAO;AAGpC,QAAI,KAAK,QAAQ,kBAAkB,QAAQ,aAAa;AACtD,YAAM,KAAK,cAAc,OAAO;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBACJ,WACA,WACe;AACf,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI,MAAM,WAAW,SAAS,YAAY;AAAA,IAClD;AAEA,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,UAAM,EAAE,gBAAgB,IAAI;AAE5B,UAAM,QAAQ,eAAe;AAAA,MAC3B,IAAI,gBAAgB,UAAU,WAAW,UAAU,UAAU,GAAG;AAAA,IAClE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,WAAkC;AACnD,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,QAAS;AAEd,SAAK,IAAI,QAAQ,0BAA0B,SAAS,EAAE;AACtD,YAAQ,QAAQ;AAGhB,QAAI,QAAQ,UAAU;AACpB,UAAI;AACF,cAAM,QAAQ,SAAS,KAAK;AAAA,MAC9B,SAAS,KAAK;AACZ,aAAK,IAAI,SAAS,4BAA4B,GAAG,EAAE;AAAA,MACrD;AACA,cAAQ,WAAW;AAAA,IACrB;AAGA,QAAI,QAAQ,aAAa;AACvB,UAAI;AACF,gBAAQ,YAAY,MAAM;AAAA,MAC5B,SAAS,KAAK;AACZ,aAAK,IAAI,SAAS,+BAA+B,GAAG,EAAE;AAAA,MACxD;AACA,cAAQ,cAAc;AAAA,IACxB;AAIA,QAAI,QAAQ,iBAAiB;AAC3B,UAAI;AACF,cAAM,QAAQ,gBAAgB,KAAK;AAAA,MACrC,SAAS,KAAK;AACZ,aAAK,IAAI,SAAS,oCAAoC,GAAG,EAAE;AAAA,MAC7D;AACA,cAAQ,kBAAkB;AAAA,IAC5B;AAGA,QAAI,QAAQ,SAAS;AACnB,cAAQ,QAAQ;AAAA,IAClB;AAGA,QAAI;AACF,YAAM,QAAQ,eAAe,MAAM;AAAA,IACrC,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,kCAAkC,GAAG,EAAE;AAAA,IAC3D;AAEA,SAAK,SAAS,OAAO,SAAS;AAC9B,SAAK,KAAK,kBAAkB,EAAE,UAAU,CAAC;AAEzC,SAAK;AAAA,MACH;AAAA,MACA,kBAAkB,SAAS,6BAA6B,KAAK,SAAS,IAAI;AAAA,IAC5E;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAmC;AACjC,WAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO;AAAA,MACpD,IAAI,EAAE;AAAA,MACN,OAAO,EAAE;AAAA,MACT,WAAW,EAAE;AAAA,MACb,OAAO,EAAE,GAAG,EAAE,MAAM;AAAA,IACtB,EAAE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAA6C;AACtD,UAAM,UAAU,KAAK,SAAS,IAAI,SAAS;AAC3C,QAAI,CAAC,QAAS,QAAO;AAErB,WAAO;AAAA,MACL,IAAI,QAAQ;AAAA,MACZ,OAAO,QAAQ;AAAA,MACf,WAAW,QAAQ;AAAA,MACnB,OAAO,EAAE,GAAG,QAAQ,MAAM;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,SAAK,IAAI,QAAQ,wBAAwB;AACzC,UAAM,aAAa,MAAM,KAAK,KAAK,SAAS,KAAK,CAAC;AAClD,UAAM,QAAQ,IAAI,WAAW,IAAI,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC,CAAC;AAC/D,SAAK,IAAI,QAAQ,uBAAuB;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,eAAuB;AACzB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,oBAAoB,IAAS,WAAkC;AAC3E,QAAI,GAAG,sBAAsB,WAAY;AAEzC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAM,UAAU,WAAW,MAAM;AAC/B,gBAAQ;AAAA,MACV,GAAG,SAAS;AAEZ,SAAG,wBAAwB,UAAU,CAAC,UAAkB;AACtD,YAAI,UAAU,YAAY;AACxB,uBAAa,OAAO;AACpB,kBAAQ;AAAA,QACV;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAkB,SAAuC;AACrE,SAAK;AAAA,MACH;AAAA,MACA,sCAAsC,QAAQ,EAAE,aAAa,KAAK,QAAQ,OAAO,aAAa,KAAK,QAAQ,OAAO;AAAA,IACpH;AAIA,YAAQ,eAAe;AAAA,MACrB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ,YAAY,SACrB,EAAE,SAAS,KAAK,QAAQ,QAAQ,IAChC;AAAA,IACN;AAGA,SAAK,mBAAmB,OAAO,EAAE,MAAM,CAAC,QAAQ;AAC9C,WAAK,IAAI,SAAS,gCAAgC,QAAQ,EAAE,KAAK,GAAG,EAAE;AACtE,WAAK,aAAa,QAAQ,EAAE,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,mBAAmB,SAAuC;AACtE,QAAI,CAAC,QAAQ,cAAc;AACzB,WAAK,IAAI,QAAQ,gCAAgC,QAAQ,EAAE,EAAE;AAC7D;AAAA,IACF;AAEA,SAAK,IAAI,QAAQ,mCAAmC,QAAQ,EAAE,EAAE;AAEhE,UAAM,SAAS,MAAM,KAAK,WAAW;AACrC,UAAM,EAAE,WAAW,UAAU,IAAI;AAEjC,QAAI,iBAAiB,KAAK,MAAM,KAAK,OAAO,IAAI,KAAK;AACrD,QAAI,YAAY,KAAK,MAAM,KAAK,OAAO,IAAI,UAAU;AACrD,UAAM,iBAAiB;AACvB,QAAI,iBAAiB;AACrB,QAAI,cAAc,KAAK,IAAI;AAC3B,QAAI,0BAA0B;AAC9B,QAAI,cAAc;AAElB,QAAI;AACF,WAAK,IAAI,QAAQ,mCAAmC,QAAQ,EAAE,EAAE;AAEhE,uBAAiB,SAAS,QAAQ,cAAc;AAC9C,YAAI,QAAQ,UAAU,kBAAkB,QAAQ,UAAU,UAAU;AAClE,eAAK;AAAA,YACH;AAAA,YACA,WAAW,QAAQ,EAAE,aAAa,QAAQ,KAAK;AAAA,UACjD;AACA;AAAA,QACF;AAEA,YAAI,MAAM,OAAO;AAIf,kBAAQ,MAAM;AACd,cAAI,QAAQ,MAAM,gBAAgB,GAAG;AACnC,kBAAM,OACJ,MAAM,QAAQ,MAAM,KAAK,SAAS,IAC9B,MAAM,KAAK,SAAS,GAAG,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,CAAC,EAAE,SAAS,KAAK,IACrE;AACN,iBAAK;AAAA,cACH;AAAA,cACA,yBAAyB,QAAQ,EAAE,WAAW,MAAM,SAAS,GAAG,UAAU,MAAM,MAAM,UAAU,CAAC,SAAS,IAAI;AAAA,YAChH;AAAA,UACF;AACA,cACE,KAAK,QAAQ,eAAe,MAC5B,MAAM,QACN,MAAM,KAAK,SAAS,GACpB;AAGA,kBAAM,KAAK,sBAAsB,SAAS,MAAM;AAChD,oBAAQ,iBAAiB,QAAQ,MAAM,IAAI;AAAA,UAC7C;AAAA,QACF,OAAO;AAEL,cAAI,MAAM,MAAM;AAEd,gBAAI,CAAC,QAAQ,cAAc,MAAM,WAAW;AAC1C,oBAAM,WAAW,wBAAwB,MAAM,IAAI;AACnD,sBAAQ,aAAc,YAAY,MAAM;AACxC,mBAAK,IAAI,QAAQ,yBAAyB,QAAQ,UAAU,EAAE;AAG9D,kBACE,QAAQ,oBACR,QAAQ,iBAAiB,eAAe,QACxC;AACA,sBAAM,YAAY,KAAK,UAAU;AAAA,kBAC/B,MAAM;AAAA,kBACN,OAAO,QAAQ;AAAA,kBACf,OAAO,MAAM,SAAS;AAAA,kBACtB,QAAQ,MAAM,UAAU;AAAA,gBAC1B,CAAC;AACD,wBAAQ,iBAAiB,KAAK,SAAS;AAAA,cACzC;AAAA,YACF;AAGA,gBAAI,MAAM,gBAAgB,iBAAiB,GAAG;AAC5C,oBAAM,cAAc,MAAM,eAAe;AACzC,oBAAM,aAAa,KAAK;AAAA,gBACrB,cAAc,MAAW;AAAA,cAC5B;AACA,0BAAa,YAAY,eAAgB;AAAA,YAC3C,OAAO;AACL,0BAAa,YAAY,QAAU;AAAA,YACrC;AACA,6BAAiB,MAAM,gBAAgB;AAEvC,gBAAI,QAAQ,eAAe,QAAQ;AAGjC,oBAAM,YAAY,QAAQ,eAAe;AACzC,oBAAM,WAAW,QAAQ,eAAe;AAGxC,oBAAM,cACJ,cAAc,eACd,aAAa,eACb,aAAa;AAEf,kBAAI,CAAC,aAAa;AAEhB,oBAAI,cAAc,IAAI;AACpB,uBAAK;AAAA,oBACH;AAAA,oBACA,gDAAgD,WAAW;AAAA,kBAC7D;AAAA,gBACF;AACA;AACA;AAAA,cACF;AAGA,oBAAM,cAAc,MAAM,KAAK;AAAA,gBAC7B;AAAA,gBACA;AAAA,gBACA,MAAM;AAAA,gBACN;AAAA,gBACA;AAAA,cACF;AACA,+BAAkB,iBAAiB,cAAe;AAClD,yCAA2B;AAC3B;AACA,sBAAQ,MAAM;AACd,sBAAQ,MAAM,aAAa,MAAM,KAAK;AAAA,YACxC,WAAW,QAAQ,eAAe,QAAQ;AAExC,oBAAM,OAAO,MAAM,KAAK;AAAA,gBACtB;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,cACF;AACA,kBAAI,MAAM;AACR;AACA;AACA,wBAAQ,MAAM;AACd,wBAAQ,MAAM,aAAa,MAAM,KAAK;AAAA,cACxC;AAAA,YACF;AAIA,kBAAM,MAAM,KAAK,IAAI;AACrB,gBAAI,MAAM,eAAe,KAAM;AAC7B,mBAAK;AAAA,gBACH;AAAA,gBACA,kBAAkB,QAAQ,EAAE,KAAK,QAAQ,UAAU,WAAW,QAAQ,MAAM,WAAW,kBAAkB,uBAAuB,aAAa,KAAK,MAAM,QAAQ,MAAM,YAAY,IAAI,CAAC,sBAAsB,QAAQ,MAAM,WAAW;AAAA,cACxO;AACA,4BAAc;AACd,wCAA0B;AAAA,YAC5B;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK;AAAA,QACH;AAAA,QACA,oCAAoC,QAAQ,EAAE,KAAK,GAAG;AAAA,MACxD;AAAA,IACF;AAEA,SAAK,IAAI,QAAQ,mCAAmC,QAAQ,EAAE,EAAE;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,sBACZ,SACA,QACe;AACf,QAAI,QAAQ,oBAAoB,OAAW;AAE3C,UAAM,EAAE,WAAW,UAAU,IAAI;AACjC,UAAM,OAAO,QAAQ,YAAY,QAAQ,KAAK,MAAM,KAAK,OAAO,IAAI,UAAU;AAE9E,UAAM,aAAa,IAAI,oBAAoB;AAAA,MACzC,GAAI,KAAK,QAAQ,aAAa,EAAE,YAAY,KAAK,QAAQ,WAAW,IAAI,CAAC;AAAA,MACzE,QAAQ,CAAC,OAAO,QAAQ,KAAK,IAAI,OAAO,GAAG;AAAA,IAC7C,CAAC;AAED,YAAQ,mBAAmB,KAAK,MAAM,KAAK,OAAO,IAAI,KAAM;AAC5D,YAAQ,wBAAwB;AAEhC,eAAW,GAAG,UAAU,CAAC,EAAE,SAAS,WAAW,OAAO,MAAM;AAC1D,UAAI;AAIF,YACE,QAAQ,0BAA0B,UAClC,QAAQ,0BAA0B,GAClC;AACA,kBAAQ,wBAAwB;AAAA,QAClC;AACA,cAAM,UAAW,YAAY,QAAQ,0BAA2B;AAChE,cAAM,MAAM,QAAQ;AACpB,gBAAQ,mBAAoB,MAAM,IAAK;AAEvC,cAAM,SAAS,IAAI,UAAU;AAAA,UAC3B,SAAS;AAAA,UACT,SAAS;AAAA,UACT,WAAW;AAAA,UACX;AAAA;AAAA;AAAA;AAAA,UAIA,aAAa;AAAA,UACb,gBAAgB;AAAA,UAChB,WAAW;AAAA,UACX;AAAA,QACF,CAAC;AACD,cAAM,MAAM,IAAI,UAAU,QAAQ,OAAO;AACzC,gBAAQ,YAAY,SAAS,GAAG;AAChC,YAAI,CAAC,QAAQ,oBAAoB;AAC/B,kBAAQ,qBAAqB;AAC7B,eAAK;AAAA,YACH;AAAA,YACA,yBAAyB,QAAQ,EAAE,kBAAkB,IAAI;AAAA,UAC3D;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,aAAK;AAAA,UACH;AAAA,UACA,6BAA6B,QAAQ,EAAE,KAAM,IAAc,OAAO;AAAA,QACpE;AAAA,MACF;AAAA,IACF,CAAC;AAED,eAAW,GAAG,SAAS,CAAC,QAAQ;AAC9B,WAAK,IAAI,SAAS,8BAA8B,QAAQ,EAAE,KAAK,IAAI,OAAO,EAAE;AAAA,IAC9E,CAAC;AACD,eAAW,GAAG,QAAQ,CAAC,SAAS;AAC9B,WAAK,IAAI,QAAQ,4BAA4B,IAAI,SAAS,QAAQ,EAAE,EAAE;AAAA,IACxE,CAAC;AAED,QAAI;AACF,YAAM,WAAW,MAAM;AACvB,cAAQ,kBAAkB;AAAA,IAC5B,SAAS,KAAK;AACZ,WAAK;AAAA,QACH;AAAA,QACA,wCAAwC,QAAQ,EAAE,KAAM,IAAc,OAAO;AAAA,MAC/E;AACA,cAAQ,kBAAkB;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,cACZ,SACA,QACA,WACA,gBACA,WACiB;AAEjB,UAAM,SAAS,gBAAoB,SAAS;AAC5C,UAAM,WAAW,yBAA6B,MAAM;AAGpD,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,WAAqB,CAAC;AAE5B,eAAW,OAAO,UAAU;AAC1B,YAAM,KAAK,IAAI,CAAC,KAAK,KAAK;AAC1B,eAAS,KAAK,CAAC;AACf,UAAI,MAAM,GAAG;AACX,iBAAS;AACT,gBAAQ,cAAc;AAAA,MACxB;AACA,UAAI,MAAM,GAAG;AACX,iBAAS;AACT,gBAAQ,cAAc;AAAA,MACxB;AACA,UAAI,MAAM,EAAG,UAAS;AAAA,IACxB;AAGA,QAAI,QAAQ,MAAM,cAAc,IAAI;AAClC,WAAK;AAAA,QACH;AAAA,QACA,2BAA2B,SAAS,KAAK,GAAG,CAAC;AAAA,MAC/C;AAAA,IACF;AAGA,UAAM,aAAa;AACnB,QAAI,UAAU;AAGd,QAAI,WAAW,CAAC,UAAU,CAAC,SAAS;AAClC,YAAM,UAAoB,CAAC;AAC3B,UAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAQ,KAAK,QAAQ,WAAW;AAChC,aAAK,IAAI,SAAS,oCAAoC;AAAA,MACxD;AACA,UAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAQ,KAAK,QAAQ,WAAW;AAChC,aAAK,IAAI,SAAS,oCAAoC;AAAA,MACxD;AACA,UAAI,QAAQ,SAAS,GAAG;AACtB,kBAAU,CAAC,GAAG,SAAS,GAAG,QAAQ;AAAA,MACpC,WAAW,CAAC,QAAQ,eAAe,CAAC,QAAQ,aAAa;AAEvD,aAAK;AAAA,UACH;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,qBAAqB;AAChC,UAAI,UAAU,QAAQ,eAAe,QAAQ,aAAa;AACxD,gBAAQ,sBAAsB;AAC9B,aAAK;AAAA,UACH;AAAA,UACA;AAAA,QACF;AAAA,MAEF,WAAW,QAAQ;AACjB,aAAK;AAAA,UACH;AAAA,UACA;AAAA,QACF;AACA,eAAO;AAAA,MACT,OAAO;AAEL,YAAI,QAAQ,MAAM,cAAc,GAAG;AACjC,eAAK;AAAA,YACH;AAAA,YACA,oBAAoB,QAAQ,MAAM,WAAW;AAAA,UAC/C;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAEA,QAAI,mBAAmB;AACvB,QAAI,gBAAgB;AAGpB,UAAM,OAAO,QAAQ,WAAW,QAAQ;AAExC,aAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,YAAM,UAAU,QAAQ,CAAC;AACzB,UAAI,QAAQ,WAAW,EAAG;AAE1B,YAAM,aAAa,MAAM,QAAQ,SAAS;AAC1C,YAAM,UAAU,eAAe,OAAO;AAGtC,UAAI,YAAY,EAAG;AAGnB,YAAM,aAAa,KAAK;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAGA,UAAI,QAAQ,MAAM,cAAc,GAAG;AACjC,aAAK;AAAA,UACH;AAAA,UACA,OAAO,CAAC,UAAU,OAAO,UAAU,QAAQ,MAAM,gBAAgB,WAAW,MAAM;AAAA,QACpF;AAAA,MACF;AAEA,iBAAW,aAAa,YAAY;AAClC,YAAI;AACF,kBAAQ,WAAW,SAAS,SAAS;AACrC,0BAAiB,gBAAgB,IAAK;AACtC;AAAA,QACF,SAAS,KAAK;AACZ,eAAK;AAAA,YACH;AAAA,YACA,wCAAwC,QAAQ,EAAE,KAAK,GAAG;AAAA,UAC5D;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AAGA,QAAI,QAAQ,MAAM,cAAc,GAAG;AACjC,WAAK;AAAA,QACH;AAAA,QACA,8BAA8B,QAAQ,MAAM,YAAY,gBAAgB,QAAQ,cAAc,KAAK,aAAa,OAAO,SAAS,aAAa,UAAU;AAAA,MACzJ;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,6BACZ,SACA,OACA,aACA,OACkB;AAClB,QAAI,CAAC,QAAQ,kBAAkB;AAC7B,UAAI,gBAAgB,GAAG;AACrB,aAAK,IAAI,QAAQ,qCAAqC,QAAQ,EAAE,EAAE;AAAA,MACpE;AACA,aAAO;AAAA,IACT;AAEA,QAAI,QAAQ,iBAAiB,eAAe,QAAQ;AAClD,UAAI,gBAAgB,GAAG;AACrB,aAAK;AAAA,UACH;AAAA,UACA,2CAA2C,QAAQ,EAAE,KAAK,QAAQ,iBAAiB,UAAU;AAAA,QAC/F;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,oBAAoB,MAAM,IAAI;AAC/C,QAAI,aAAa,MAAM,eAAe;AACtC,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,WAAqB,CAAC;AAE5B,eAAW,WAAW,UAAU;AAC9B,UAAI,QAAQ,WAAW,EAAG;AAE1B,UAAI,UAAU,QAAQ;AACpB,cAAM,UAAUD,gBAAe,OAAO;AACtC,iBAAS,KAAK,OAAO;AAErB,YAAI,YAAY,IAAI;AAClB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,IAAI;AAClB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,IAAI;AAClB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,MAAM,YAAY,IAAI;AACpC,mBAAS;AACT,uBAAa;AAAA,QACf;AAAA,MACF,OAAO;AAEL,cAAM,UAAU,eAAe,OAAO;AACtC,iBAAS,KAAK,OAAO;AAErB,YAAI,YAAY,GAAG;AACjB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,GAAG;AACjB,mBAAS;AACT,kBAAQ,cAAc;AAAA,QACxB;AACA,YAAI,YAAY,GAAG;AACjB,mBAAS;AACT,uBAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAGA,QAAI,cAAc,GAAG;AACnB,WAAK;AAAA,QACH;AAAA,QACA,GAAG,KAAK,UAAU,WAAW,gBAAgB,SAAS,KAAK,GAAG,CAAC,YAAY,MAAM,WAAW,MAAM,WAAW,MAAM;AAAA,MACrH;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,qBAAqB;AAChC,UAAI,UAAU,QAAQ;AAEpB,YAAI,UAAU,QAAQ,eAAe,QAAQ,aAAa;AACxD,kBAAQ,sBAAsB;AAC9B,eAAK;AAAA,YACH;AAAA,YACA;AAAA,UACF;AAAA,QACF,WAAW,UAAU,QAAQ;AAE3B,eAAK,IAAI,SAAS,gDAAgD;AAClE,iBAAO;AAAA,QACT,WAAW,QAAQ;AAEjB,eAAK,IAAI,SAAS,sCAAsC;AACxD,iBAAO;AAAA,QACT,OAAO;AAEL,cAAI,cAAc,IAAI;AACpB,iBAAK;AAAA,cACH;AAAA,cACA,0BAA0B,WAAW;AAAA,YACvC;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,MACF,OAAO;AAEL,YACE,UACA,QAAQ,eACR,QAAQ,eACR,QAAQ,aACR;AACA,kBAAQ,sBAAsB;AAC9B,eAAK;AAAA,YACH;AAAA,YACA;AAAA,UACF;AAAA,QACF,WAAW,UAAU,UAAU,QAAQ;AACrC,eAAK,IAAI,SAAS,gDAAgD;AAClE,iBAAO;AAAA,QACT,WAAW,QAAQ;AACjB,eAAK,IAAI,SAAS,gDAAgD;AAClE,iBAAO;AAAA,QACT,OAAO;AACL,cAAI,cAAc,IAAI;AACpB,iBAAK;AAAA,cACH;AAAA,cACA,0BAA0B,WAAW;AAAA,YACvC;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,QAAI,YAAY,MAAM;AAEtB,QAAI,QAAQ;AACV,UAAI,UAAU,WAAW,CAAC,UAAU,CAAC,SAAS;AAE5C,cAAM,QAAkB,CAAC;AACzB,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,MAAM,SAAS,GAAG;AACpB,sBAAY,OAAO,OAAO,CAAC,GAAG,OAAO,MAAM,IAAI,CAAC;AAChD,eAAK,IAAI,SAAS,6CAA6C;AAAA,QACjE;AAAA,MACF,WAAW,UAAU,WAAW,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS;AAE9D,cAAM,QAAkB,CAAC;AACzB,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,CAAC,UAAU,QAAQ,aAAa;AAClC,gBAAM,KAAK,OAAO,KAAK,CAAC,GAAM,GAAM,GAAM,CAAI,CAAC,CAAC;AAChD,gBAAM,KAAK,QAAQ,WAAW;AAAA,QAChC;AACA,YAAI,MAAM,SAAS,GAAG;AACpB,sBAAY,OAAO,OAAO,CAAC,GAAG,OAAO,MAAM,IAAI,CAAC;AAChD,eAAK,IAAI,SAAS,iDAAiD;AAAA,QACrE;AAAA,MACF;AAAA,IACF;AAGA,UAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,WAAO,cAAc,aAAa,CAAC;AACnC,WAAO,cAAc,MAAM,eAAe,MAAM,eAAe,MAAO,GAAG,CAAC;AAC1E,WAAO,WAAW,UAAU,SAAS,IAAO,GAAM,CAAC;AACnD,WAAO,WAAW,aAAa,IAAI,GAAG,CAAC;AACvC,WAAO,cAAc,GAAG,EAAE;AAG1B,UAAM,SAAS,OAAO,OAAO,CAAC,QAAQ,SAAS,CAAC;AAGhD,QAAI,cAAc,GAAG;AACnB,WAAK;AAAA,QACH;AAAA,QACA,WAAW,KAAK,UAAU,WAAW,KAAK,OAAO,MAAM,oBAAoB,UAAU;AAAA,MACvF;AAAA,IACF;AAYA,UAAM,mBAAmB;AACzB,UAAM,wBAAwB,OAAQ;AAEtC,QAAI;AACF,YAAM,cAAc,KAAK;AAAA,QACvB;AAAA,QACA,KAAK,KAAK,OAAO,SAAS,qBAAqB;AAAA,MACjD;AACA,eAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,cAAM,QAAQ,IAAI;AAClB,cAAM,MAAM,KAAK,IAAI,QAAQ,uBAAuB,OAAO,MAAM;AACjE,cAAM,QAAQ,OAAO,SAAS,OAAO,GAAG;AACxC,cAAM,cAAc,OAAO,MAAM,gBAAgB;AACjD,oBAAY,cAAc,GAAG,CAAC;AAC9B,oBAAY,cAAc,aAAa,CAAC;AACxC,gBAAQ,iBAAiB,KAAK,OAAO,OAAO,CAAC,aAAa,KAAK,CAAC,CAAC;AAAA,MACnE;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,iBAAiB,KAAK,UAAU,WAAW,KAAK,GAAG,EAAE;AACvE,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,cACZ,SACA,OACA,aACe;AACf,QAAI,CAAC,QAAQ,kBAAkB;AAC7B,UAAI,gBAAgB,GAAG;AACrB,aAAK,IAAI,QAAQ,qCAAqC,QAAQ,EAAE,EAAE;AAAA,MACpE;AACA;AAAA,IACF;AAEA,QAAI,QAAQ,iBAAiB,eAAe,QAAQ;AAClD,UAAI,gBAAgB,GAAG;AACrB,aAAK;AAAA,UACH;AAAA,UACA,2CAA2C,QAAQ,EAAE,KAAK,QAAQ,iBAAiB,UAAU;AAAA,QAC/F;AAAA,MACF;AACA;AAAA,IACF;AAGA,QAAI,aAAa,MAAM,eAAe;AAGtC,QAAI,CAAC,cAAc,MAAM,eAAe,QAAW;AACjD,YAAM,WAAW,oBAAoB,MAAM,IAAI;AAC/C,iBAAW,WAAW,UAAU;AAC9B,YAAI,QAAQ,WAAW,EAAG;AAC1B,cAAM,UAAUA,gBAAe,OAAO;AAEtC,YACE,YAAY,MACZ,YAAY,MACZ,YAAY,MACZ,YAAY,MACZ,YAAY,IACZ;AACA,uBAAa;AACb;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,SAAS,OAAO,MAAM,EAAE;AAC9B,WAAO,cAAc,aAAa,CAAC;AACnC,WAAO,cAAc,MAAM,eAAe,MAAM,eAAe,MAAO,GAAG,CAAC;AAC1E,WAAO,WAAW,GAAM,CAAC;AACzB,WAAO,WAAW,aAAa,IAAI,GAAG,CAAC;AACvC,WAAO,cAAc,GAAG,EAAE;AAG1B,UAAM,SAAS,OAAO,OAAO,CAAC,QAAQ,MAAM,IAAI,CAAC;AAGjD,QAAI,cAAc,GAAG;AACnB,WAAK;AAAA,QACH;AAAA,QACA,uBAAuB,WAAW,KAAK,OAAO,MAAM,oBAAoB,UAAU;AAAA,MACpF;AAAA,IACF;AAGA,UAAM,iBAAiB;AAEvB,QAAI;AACF,UAAI,OAAO,UAAU,gBAAgB;AACnC,gBAAQ,iBAAiB,KAAK,MAAM;AAAA,MACtC,OAAO;AAEL,cAAM,cAAc,KAAK,KAAK,OAAO,SAAS,cAAc;AAC5D,iBAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,gBAAM,QAAQ,IAAI;AAClB,gBAAM,MAAM,KAAK,IAAI,QAAQ,gBAAgB,OAAO,MAAM;AAC1D,gBAAM,QAAQ,OAAO,SAAS,OAAO,GAAG;AAGxC,gBAAM,cAAc,OAAO,MAAM,CAAC;AAClC,sBAAY,WAAW,GAAG,CAAC;AAC3B,sBAAY,WAAW,aAAa,CAAC;AAErC,kBAAQ,iBAAiB,KAAK,OAAO,OAAO,CAAC,aAAa,KAAK,CAAC,CAAC;AAAA,QACnE;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,SAAS,6BAA6B,WAAW,KAAK,GAAG,EAAE;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,qBACN,QACA,SACA,gBACA,WACA,QACA,MACO;AACP,UAAM,EAAE,WAAW,UAAU,IAAI;AACjC,UAAM,MAAM;AAEZ,UAAM,UAAiB,CAAC;AAExB,QAAI,QAAQ,UAAU,KAAK;AAEzB,YAAM,SAAS,IAAI,UAAU;AAC7B,aAAO,cAAc;AACrB,aAAO,iBAAiB;AACxB,aAAO,YAAY;AACnB,aAAO,SAAS;AAChB,aAAO,OAAO;AAEd,cAAQ,KAAK,IAAI,UAAU,QAAQ,OAAO,CAAC;AAAA,IAC7C,OAAO;AAEL,YAAM,YAAY,QAAQ,CAAC;AAC3B,YAAM,UAAU,YAAY;AAC5B,YAAM,MAAM,YAAY;AAGxB,YAAM,eAAe,MAAM,MAAM;AAEjC,UAAI,SAAS;AACb,UAAI,UAAU;AAEd,aAAO,SAAS,QAAQ,QAAQ;AAC9B,cAAM,YAAY,QAAQ,SAAS;AACnC,cAAM,YAAY,KAAK,IAAI,WAAW,MAAM,CAAC;AAC7C,cAAM,SAAS,SAAS,aAAa,QAAQ;AAG7C,YAAI,WAAW;AACf,YAAI,QAAS,aAAY;AACzB,YAAI,OAAQ,aAAY;AAGxB,cAAM,YAAY,OAAO,MAAM,IAAI,SAAS;AAC5C,kBAAU,CAAC,IAAI;AACf,kBAAU,CAAC,IAAI;AACf,gBAAQ,KAAK,WAAW,GAAG,QAAQ,SAAS,SAAS;AAErD,cAAM,SAAS,IAAI,UAAU;AAC7B,eAAO,cAAc;AACrB,eAAO,iBAAkB,iBAAiB,QAAQ,SAAU;AAC5D,eAAO,YAAY;AACnB,eAAO,SAAS,UAAU;AAC1B,eAAO,OAAO;AAEd,gBAAQ,KAAK,IAAI,UAAU,QAAQ,SAAS,CAAC;AAE7C,kBAAU;AACV,kBAAU;AAAA,MACZ;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,cAAc,SAAuC;AACjE,QAAI;AACF,cAAQ,WAAW,IAAI,SAAS;AAAA,QAC9B,KAAK,KAAK,QAAQ;AAAA,QAClB,SAAS,KAAK,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,QAAQ,SAAS,MAAM;AAC7B,WAAK,IAAI,QAAQ,gCAAgC,QAAQ,EAAE,EAAE;AAAA,IAC/D,SAAS,KAAK;AACZ,WAAK;AAAA,QACH;AAAA,QACA,wCAAwC,QAAQ,EAAE,KAAK,GAAG;AAAA,MAC5D;AAEA,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,IACN,OACA,SACM;AACN,QAAI,KAAK,QAAQ,QAAQ;AACvB,WAAK,QAAQ,OAAO,OAAO,OAAO;AAAA,IACpC;AAAA,EACF;AACF;;;AEp+CA,SAAS,gBAAAE,qBAAoB;AAC7B,OAAO,QAAQ;AACf,OAAO,SAAS;AAChB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,SAAAC,cAAgC;AA+DzC,SAASC,qBAAoB,MAAwB;AACnD,QAAM,QAAkB,CAAC;AACzB,QAAM,MAAM,KAAK;AAEjB,QAAM,YAAY,CAAC,SAAyB;AAC1C,aAAS,IAAI,MAAM,IAAI,IAAI,KAAK,KAAK;AACnC,UAAI,KAAK,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,GAAM;AAC5C,YAAI,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AACjC,YAAI,IAAI,IAAI,OAAO,KAAK,IAAI,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM;AACzD,iBAAO;AAAA,MACX;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,iBAAiB,CAAC,MAAsB;AAC5C,QAAI,IAAI,IAAI,OAAO,KAAK,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,GAAM;AAC3D,UAAI,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AACjC,UAAI,IAAI,IAAI,OAAO,KAAK,IAAI,CAAC,MAAM,KAAQ,KAAK,IAAI,CAAC,MAAM,EAAM,QAAO;AAAA,IAC1E;AACA,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,UAAU,CAAC;AACvB,MAAI,QAAQ,EAAG,QAAO;AAEtB,SAAO,SAAS,GAAG;AACjB,UAAM,QAAQ,eAAe,KAAK;AAClC,QAAI,CAAC,MAAO;AACZ,UAAM,WAAW,QAAQ;AACzB,QAAI,OAAO,UAAU,QAAQ;AAC7B,QAAI,OAAO,EAAG,QAAO;AACrB,QAAI,WAAW,KAAM,OAAM,KAAK,KAAK,SAAS,UAAU,IAAI,CAAC;AAC7D,YAAQ,OAAO,MAAM,OAAO;AAAA,EAC9B;AAEA,SAAO;AACT;AAWA,SAAS,iBAAiB,OAAiB,QAAyB;AAClE,QAAM,OAAOA,qBAAoB,MAAM;AAEvC,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,OAAO,IAAI,WAAW,EAAG;AAC9B,QAAI,UAAU,QAAQ;AACpB,YAAM,UAAU,IAAI,CAAC,IAAK;AAE1B,UAAI,YAAY,EAAG,QAAO;AAAA,IAC5B,OAAO;AACL,YAAM,UAAW,IAAI,CAAC,KAAM,IAAK;AAEjC,UAAI,WAAW,MAAM,WAAW,GAAI,QAAO;AAAA,IAC7C;AAAA,EACF;AAEA,SAAO;AACT;AAKA,SAAS,aAAa,OAAiB,QAAyB;AAC9D,QAAM,OAAOA,qBAAoB,MAAM;AACvC,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,OAAO,IAAI,WAAW,EAAG;AAC9B,QAAI,UAAU,QAAQ;AACpB,YAAM,UAAU,IAAI,CAAC,IAAK;AAC1B,UAAI,YAAY,KAAK,YAAY,EAAG,QAAO;AAAA,IAC7C,OAAO;AACL,YAAM,UAAW,IAAI,CAAC,KAAM,IAAK;AACjC,UAAI,YAAY,MAAM,YAAY,MAAM,YAAY,GAAI,QAAO;AAAA,IACjE;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,YAAY,OAAiB,QAA0B;AAC9D,QAAM,OAAOA,qBAAoB,MAAM;AACvC,SAAO,KAAK,IAAI,CAAC,QAAQ;AACvB,QAAI,UAAU,QAAQ;AACpB,aAAQ,IAAI,CAAC,KAAM,IAAK;AAAA,IAC1B,OAAO;AACL,aAAO,IAAI,CAAC,IAAK;AAAA,IACnB;AAAA,EACF,CAAC;AACH;AAMO,IAAM,oBAAN,cAAgCC,cAAa;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAKT,YAA2B;AAAA,EAC3B,iBAA0B;AAAA,EAC1B,eAA8B;AAAA,EAC9B,iBAAgC;AAAA,EAEhC,QAAkC;AAAA,EAClC,QAAyB;AAAA,EACzB,iBAAyB;AAAA,EACzB,SAA8B;AAAA,EAC9B,eAA0D;AAAA,EAC1D,cAAoC;AAAA,EACpC,YAAyB;AAAA,EACzB,YAA2B;AAAA,EAEnC,YAAY,SAAmC;AAC7C,UAAM;AACN,SAAK,MAAM,QAAQ;AACnB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ;AACvB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,kBAAkB,QAAQ,mBAAmB;AAClD,SAAK,eAAe,QAAQ,gBAAgB;AAC5C,SAAK,aAAa,QAAQ,cAAc;AAExC,QAAI,QAAQ,WAAW;AACrB,WAAK,YAAY,QAAQ;AACzB,WAAK,iBAAiB;AAAA,IACxB;AAEA,SAAK,sBAAsB,QAAQ;AACnC,SAAK,MAAM,QAAQ,WAAW,MAAM;AAAA,IAAC;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,UAAU,aAAa,KAAK,UAAU,YAAY;AACzD;AAAA,IACF;AAEA,SAAK,QAAQ;AACb,SAAK,YAAY;AAEjB,QAAI;AAEF,UAAI,CAAC,KAAK,WAAW;AACnB,aAAK,YAAY,MAAM,IAAI;AAAA,UACzB,KAAK,KAAK,GAAG,OAAO,GAAG,gBAAgB,KAAK,OAAO,GAAG;AAAA,QACxD;AACA,aAAK,iBAAiB;AAAA,MACxB,OAAO;AACL,cAAM,IAAI,MAAM,KAAK,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,MACrD;AAEA,WAAK,eAAe,KAAK,KAAK,KAAK,WAAW,eAAe;AAC7D,WAAK,iBAAiB,KAAK,KAAK,KAAK,WAAW,iBAAiB;AAEjE,WAAK,IAAI,QAAQ,0BAA0B,KAAK,SAAS,EAAE;AAG3D,UAAI,KAAK,qBAAqB;AAC5B,aAAK,eAAe,KAAK,2BAA2B,KAAK,mBAAmB;AAAA,MAC9E,OAAO;AAEL,aAAK,eAAe;AAAA,UAClB,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI;AAAA,QAC7C;AAAA,MACF;AAGA,WAAK,cAAc,KAAK,mBAAmB;AAC3C,WAAK,YAAY,oBAAI,KAAK;AAC1B,WAAK,QAAQ;AAEb,WAAK,KAAK,WAAW,EAAE,WAAW,KAAK,UAAU,CAAC;AAAA,IACpD,SAAS,KAAK;AACZ,WAAK,QAAQ;AACb,WAAK,YAAY,OAAO,GAAG;AAC3B,WAAK,IAAI,SAAS,wBAAwB,GAAG,EAAE;AAC/C,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,KAAK,UAAU,UAAU,KAAK,UAAU,WAAW;AACrD;AAAA,IACF;AAEA,SAAK,QAAQ;AACb,SAAK,IAAI,QAAQ,qBAAqB;AAGtC,QAAI;AACF,WAAK,QAAQ,OAAO,IAAI;AAAA,IAC1B,QAAQ;AAAA,IAER;AAGA,QAAI;AACF,WAAK,QAAQ,KAAK,SAAS;AAAA,IAC7B,QAAQ;AAAA,IAER;AACA,SAAK,SAAS;AAGd,QAAI,KAAK,cAAc;AACrB,UAAI;AACF,cAAM,KAAK,aAAa,OAAO,MAAgB;AAAA,MACjD,QAAQ;AAAA,MAER;AACA,WAAK,eAAe;AAAA,IACtB;AAGA,QAAI,KAAK,aAAa;AACpB,UAAI;AACF,cAAM,KAAK;AAAA,MACb,QAAQ;AAAA,MAER;AACA,WAAK,cAAc;AAAA,IACrB;AAGA,QAAI,KAAK,kBAAkB,KAAK,WAAW;AACzC,UAAI;AACF,cAAM,IAAI,GAAG,KAAK,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,MAC/D,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,SAAK,QAAQ;AACb,SAAK,KAAK,SAAS;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,YAA6B;AAC3B,WAAO;AAAA,MACL,OAAO,KAAK;AAAA,MACZ,OAAO,KAAK;AAAA,MACZ,gBAAgB,KAAK;AAAA,MACrB,eAAe,KAAK,WAAW,QAAQ,CAAC,KAAK,OAAO;AAAA,MACpD,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK;AAAA,MAChB,WAAW,KAAK;AAAA,MAChB,OAAO,KAAK;AAAA,IACd;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAiC;AAC/B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,eAA8B;AAC5B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,YAAoB,KAAyB;AACjE,QAAI,CAAC,KAAK,aAAc,QAAO;AAE/B,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,WAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAI,GAAG,WAAW,KAAK,YAAY,GAAG;AACpC,eAAO;AAAA,MACT;AACA,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,IAC7C;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UACJ,WACuD;AACvD,QAAI,CAAC,KAAK,UAAW,QAAO;AAG5B,UAAM,OAAO,UAAU,QAAQ,QAAQ,EAAE;AACzC,QAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,GAAG,GAAG;AAC7C,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,KAAK,KAAK,KAAK,WAAW,IAAI;AAC/C,QAAI,CAAC,GAAG,WAAW,QAAQ,GAAG;AAC5B,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,IAAI,SAAS,QAAQ;AACxC,QAAI,cAAc;AAClB,QAAI,KAAK,SAAS,OAAO,GAAG;AAC1B,oBAAc;AAAA,IAChB,WAAW,KAAK,SAAS,KAAK,GAAG;AAC/B,oBAAc;AAAA,IAChB;AAEA,WAAO,EAAE,MAAM,YAAY;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAe,2BACb,aAOgB;AAQhB,UAAM,QAAiB,CAAC;AACxB,QAAI,UAA+B;AACnC,QAAI,OAAO;AAEX,UAAM,UAAU,CAAC,OAAc;AAC7B,YAAM,KAAK,EAAE;AACb,gBAAU;AACV,gBAAU;AAAA,IACZ;AAEA,UAAM,UAAU,MAAM;AACpB,aAAO;AACP,gBAAU;AACV,gBAAU;AAAA,IACZ;AAEA,UAAM,UAAU,MAAM;AACpB,aAAO;AACP,gBAAU;AACV,gBAAU;AAAA,IACZ;AAEA,gBAAY,GAAG,mBAAmB,OAAO;AACzC,gBAAY,GAAG,SAAS,OAAO;AAC/B,gBAAY,GAAG,SAAS,OAAO;AAE/B,QAAI;AACF,aAAO,CAAC,MAAM;AACZ,YAAI,MAAM,WAAW,GAAG;AACtB,gBAAM,IAAI,QAAc,CAAC,MAAM;AAAE,sBAAU;AAAA,UAAG,CAAC;AAAA,QACjD;AACA,eAAO,MAAM,SAAS,GAAG;AACvB,gBAAM,QAAQ,MAAM,MAAM;AAC1B,gBAAM;AAAA,YACJ,OAAO;AAAA,YACP,MAAM,MAAM;AAAA,YACZ,WAAW,MAAM;AAAA,YACjB,YAAY,MAAM;AAAA,YAClB,cAAc,MAAM;AAAA,UACtB;AAAA,QACF;AAAA,MACF;AAAA,IACF,UAAE;AACA,kBAAY,eAAe,mBAAmB,OAAO;AACrD,kBAAY,eAAe,SAAS,OAAO;AAC3C,kBAAY,eAAe,SAAS,OAAO;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,MAAc,qBAAoC;AAChD,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,gBAAgB,CAAC,KAAK,gBAAgB;AACpE;AAAA,IACF;AAEA,QAAI,gBAAgB;AACpB,QAAI,mBAA6B,CAAC;AAClC,UAAM,+BAA+B;AAErC,UAAM,mBAAmB,CAAC,OAAiB,WAAmB;AAC5D,YAAM,OAAOD,qBAAoB,MAAM;AACvC,iBAAW,OAAO,MAAM;AACtB,YAAI,CAAC,OAAO,IAAI,WAAW,EAAG;AAC9B,YAAI,UAAU,QAAQ;AACpB,gBAAM,IAAI,IAAI,CAAC,IAAK;AACpB,cAAI,MAAM,KAAK,MAAM,GAAG;AACtB,6BAAiB;AAAA,cACf,OAAO,OAAO,CAAC,OAAO,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AAAA,YAChD;AAAA,UACF;AAAA,QACF,OAAO;AACL,gBAAM,IAAK,IAAI,CAAC,KAAM,IAAK;AAC3B,cAAI,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AACpC,6BAAiB;AAAA,cACf,OAAO,OAAO,CAAC,OAAO,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AAAA,YAChD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,UAAI,iBAAiB,SAAS,IAAI;AAChC,2BAAmB,iBAAiB,MAAM,GAAG;AAAA,MAC/C;AAAA,IACF;AAEA,QAAI;AACF,uBAAiB,SAAS,KAAK,cAAc;AAC3C,YAAI,KAAK,UAAU,UAAW;AAG9B,YAAI,MAAM,MAAO;AACjB,YAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,WAAW,EAAG;AAG5C,YAAI,CAAC,KAAK,OAAO;AACf,gBAAM,WAAW,wBAAwB,MAAM,IAAI;AACnD,gBAAM,WACJ,MAAM,cAAc,SAAS,SAAS;AACxC,eAAK,QAAQ,WACR,SAAS,YAAY,IACtB;AACJ,eAAK;AAAA,YACH;AAAA,YACA,4BAA4B,QAAQ,aAAa,QAAQ,WAAW,KAAK,KAAK;AAAA,UAChF;AACA,eAAK,KAAK,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,QACnD;AAGA,cAAM,SACJ,KAAK,UAAU,SACXE,iBAAoB,MAAM,IAAI,IAC9B,gBAAoB,MAAM,IAAI;AAEpC,aAAK;AAGL,cAAM,YACJ,KAAK,kBAAkB,KACtB,KAAK,kBAAkB,MAAM,KAAK,iBAAiB,OAAO;AAC7D,YAAI,WAAW;AACb,gBAAM,WAAW,YAAY,KAAK,OAAO,MAAM;AAC/C,gBAAM,SAAS,iBAAiB,KAAK,OAAO,MAAM;AAClD,gBAAM,YAAY,aAAa,KAAK,OAAO,MAAM;AACjD,eAAK;AAAA,YACH;AAAA,YACA,aAAa,KAAK,cAAc,WAAW,OAAO,MAAM,cAAc,SAAS,KAAK,GAAG,CAAC,YAAY,MAAM,cAAc,SAAS;AAAA,UACnI;AAAA,QACF;AAEA,yBAAiB,KAAK,OAAO,MAAM;AAKnC,cAAM,aAAa,iBAAiB,KAAK,OAAO,MAAM;AAGtD,YAAI,CAAC,cAAc,CAAC,eAAe;AACjC,cAAI,KAAK,iBAAiB,8BAA8B;AACtD;AAAA,UACF;AACA,eAAK;AAAA,YACH;AAAA,YACA,qBAAqB,KAAK,cAAc;AAAA,UAC1C;AAAA,QACF;AAEA,YAAI,CAAC,eAAe;AAClB,eAAK;AAAA,YACH;AAAA,YACA,0BAA0B,KAAK,KAAK,eAAe,KAAK,cAAc,eAAe,UAAU,cAAc,iBAAiB,MAAM;AAAA,UACtI;AACA,eAAK,SAAS,KAAK,YAAY;AAC/B,0BAAgB;AAChB,eAAK,KAAK,gBAAgB;AAG1B,cAAI;AACF,gBAAI,KAAK,QAAQ,SAAS,CAAC,KAAK,OAAO,MAAM,WAAW;AACtD,yBAAW,MAAM,kBAAkB;AACjC,qBAAK,OAAO,MAAM,MAAM,EAAE;AAAA,cAC5B;AAAA,YACF;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,YAAI,CAAC,KAAK,UAAU,CAAC,KAAK,OAAO,SAAS,KAAK,OAAO,MAAM,WAAW;AACrE,eAAK,IAAI,QAAQ,2CAA2C;AAC5D;AAAA,QACF;AAEA,YAAI;AACF,eAAK,OAAO,MAAM,MAAM,MAAM;AAE9B,cACE,KAAK,iBAAiB,QAAQ,KAC9B,KAAK,kBAAkB,KACtB,KAAK,kBAAkB,MAAM,KAAK,iBAAiB,OAAO,GAC3D;AACA,iBAAK;AAAA,cACH;AAAA,cACA,kBAAkB,KAAK,cAAc,eAAe,OAAO,MAAM;AAAA,YACnE;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,eAAK,IAAI,SAAS,8BAA8B,GAAG,EAAE;AACrD;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,GAAG;AACV,WAAK,IAAI,SAAS,mBAAmB,CAAC,EAAE;AACxC,WAAK,YAAY,OAAO,CAAC;AACzB,WAAK,QAAQ;AACb,WAAK,KAAK,SAAS,CAAC;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,cAA4B;AAClC,QAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,gBAAgB;AAC9C,YAAM,IAAI,MAAM,uBAAuB;AAAA,IACzC;AAEA,UAAM,QAAQ,KAAK,SAAS;AAE5B,UAAM,OAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,SAAS,SAAS;AAAA,MAC5B;AAAA,MACA;AAAA,IACF;AAEA,QAAI,UAAU,QAAQ;AAEpB,WAAK;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AAEL,WAAK,KAAK,QAAQ,MAAM;AAAA,IAC1B;AAEA,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,KAAK,eAAe;AAAA,MAC3B;AAAA,MACA,OAAO,KAAK,YAAY;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,KAAK;AAAA,IACP;AAEA,UAAM,IAAIC,OAAM,KAAK,YAAY,MAAM;AAAA,MACrC,OAAO,CAAC,QAAQ,UAAU,MAAM;AAAA,IAClC,CAAC;AAED,MAAE,GAAG,SAAS,CAAC,QAAQ;AACrB,WAAK,IAAI,SAAS,uBAAuB,GAAG,EAAE;AAC9C,WAAK,KAAK,gBAAgB,GAAG;AAAA,IAC/B,CAAC;AAED,MAAE,QAAQ,GAAG,QAAQ,CAAC,MAAM;AAC1B,YAAM,IAAI,OAAO,KAAK,EAAE,EAAE,KAAK;AAC/B,UAAI,EAAG,MAAK,IAAI,QAAQ,YAAY,CAAC,EAAE;AAAA,IACzC,CAAC;AAED,MAAE,GAAG,QAAQ,CAAC,MAAM,WAAW;AAC7B,WAAK;AAAA,QACH;AAAA,QACA,uBAAuB,QAAQ,GAAG,WAAW,UAAU,GAAG;AAAA,MAC5D;AACA,WAAK,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAAA,IAC7C,CAAC;AAED,WAAO;AAAA,EACT;AACF;;;AC3tBA,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,SAAAC,cAAa;AACtB,YAAYC,UAAS;AAsBd,IAAM,sBAAN,cAAkCF,cAKtC;AAAA,EACO;AAAA,EACA,kBAA0C;AAAA,EAC1C,aAAgC;AAAA,EAChC,gBAAiD;AAAA,EACjD,SAAS;AAAA,EACT;AAAA,EACA,mBAAmB,oBAAI,IAAY;AAAA,EAE3C,YAAY,SAAqC;AAC/C,UAAM;AACN,SAAK,UAAU;AAAA,MACb,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,GAAG;AAAA,IACL;AACA,SAAK,SAAS,QAAQ,UAAU;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAQ;AACf,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,SAAK,SAAS;AACd,SAAK,OAAO,MAAM,yDAAyD;AAE3E,QAAI;AAEF,WAAK,kBAAkB,IAAI,gBAAgB;AAAA,QACzC,KAAK,KAAK,QAAQ;AAAA,QAClB,cAAc,KAAK,QAAQ;AAAA,QAC3B,aAAa,KAAK,QAAQ;AAAA,QAC1B,cAAc,KAAK,QAAQ;AAAA,QAC3B,aAAa,KAAK,QAAQ;AAAA,QAC1B,GAAI,KAAK,QAAQ,cAAc,EAAE,aAAa,KAAK,QAAQ,YAAY,IAAI,CAAC;AAAA,QAC5E,GAAI,KAAK,QAAQ,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,QAAQ,IAAI,CAAC;AAAA,QAC9E,GAAI,KAAK,QAAQ,cAAc,SAAY,EAAE,WAAW,KAAK,QAAQ,UAAU,IAAI,CAAC;AAAA,QACpF,QAAQ,KAAK;AAAA,MACf,CAAC;AAED,WAAK,gBAAgB,GAAG,SAAS,CAAC,UAAU;AAC1C,aAAK,OAAO,QAAQ,iDAAiD,KAAK;AAC1E,aAAK,KAAK,SAAS,KAAK;AAAA,MAC1B,CAAC;AAED,WAAK,gBAAgB,GAAG,SAAS,MAAM;AACrC,aAAK,OAAO,MAAM,+CAA+C;AAAA,MACnE,CAAC;AAED,YAAM,KAAK,gBAAgB,MAAM;AAGjC,YAAM,KAAK,gBAAgB;AAE3B,WAAK,OAAO;AAAA,QACV,2CAA2C,KAAK,QAAQ,UAAU,IAAI,KAAK,QAAQ,UAAU,GAAG,KAAK,QAAQ,IAAI;AAAA,MACnH;AAAA,IACF,SAAS,OAAO;AACd,WAAK,SAAS;AACd,WAAK,KAAK,SAAS,KAAc;AACjC,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,kBAAiC;AAE7C,UAAM,gBAAgB,MAAM,KAAK,QAAQ,IAAI,kBAAkB,KAAK,QAAQ,YAAY;AACxF,UAAM,kBAAkB,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAY,KAAK,QAAQ,YAAY;AACjG,UAAM,QAAQ,iBAAiB,SAAS;AACxC,UAAM,SAAS,iBAAiB,UAAU;AAC1C,UAAM,MAAM,iBAAiB,aAAa;AAG1C,SAAK,aAAiB,kBAAa,CAAC,WAAW;AAC7C,WAAK,qBAAqB,MAAM;AAAA,IAClC,CAAC;AAED,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAK,WAAY,OAAO,KAAK,QAAQ,YAAY,KAAK,QAAQ,YAAY,MAAM;AAC9E,cAAM,UAAU,KAAK,WAAY,QAAQ;AACzC,YAAI,WAAW,OAAO,YAAY,YAAY,UAAU,SAAS;AAC/D,UAAC,KAAK,QAAgB,aAAa,QAAQ;AAAA,QAC7C;AACA,gBAAQ;AAAA,MACV,CAAC;AACD,WAAK,WAAY,GAAG,SAAS,MAAM;AAAA,IACrC,CAAC;AAGD,SAAK,sBAAsB,OAAO,QAAQ,GAAG;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB,OAAe,QAAgB,KAAmB;AAC9E,UAAM,UAAU,UAAU,KAAK,QAAQ,UAAU,IAAI,KAAK,QAAQ,UAAU,GAAG,KAAK,QAAQ,IAAI;AAGhG,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MAAa;AAAA,MACb;AAAA,MAAM;AAAA,MACN;AAAA,MAAM;AAAA,MACN;AAAA,MAAQ;AAAA,MACR;AAAA,MAAM;AAAA,MACN;AAAA,MAAe;AAAA,MACf;AAAA,IACF;AAEA,SAAK,OAAO;AAAA,MACV,sDAAsD,WAAW,KAAK,GAAG,CAAC;AAAA,IAC5E;AAEA,SAAK,gBAAgBC,OAAM,UAAU,YAAY;AAAA,MAC/C,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAChC,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,UAAU;AACxC,WAAK,OAAO,QAAQ,uCAAuC,KAAK;AAChE,WAAK,KAAK,SAAS,KAAK;AAAA,IAC1B,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,SAAS;AACvC,UAAI,SAAS,KAAK,SAAS,MAAM;AAC/B,aAAK,OAAO,OAAO,iDAAiD,IAAI,EAAE;AAAA,MAC5E;AAAA,IACF,CAAC;AAGD,SAAK,gBAAiB,GAAG,cAAc,CAAC,UAAkB;AACxD,UAAI,KAAK,eAAe,SAAS,CAAC,KAAK,cAAc,MAAM,WAAW;AACpE,YAAI;AACF,gBAAM,UAAU,KAAK,cAAc,MAAM,MAAM,KAAK;AACpD,cAAI,CAAC,SAAS;AACZ,iBAAK,cAAc,MAAM,KAAK,SAAS,MAAM;AAAA,YAAC,CAAC;AAAA,UACjD;AAAA,QACF,SAAS,OAAO;AACd,gBAAM,OAAQ,OAAe;AAC7B,cAAI,SAAS,WAAW,SAAS,8BAA8B;AAC7D,iBAAK,OAAO,QAAQ,kDAAkD,KAAK;AAAA,UAC7E;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAGD,SAAK,cAAc,QAAQ,GAAG,QAAQ,CAAC,SAAiB;AACtD,YAAM,SAAS,KAAK,SAAS;AAC7B,UAAI,OAAO,SAAS,OAAO,KAAK,OAAO,SAAS,OAAO,GAAG;AACxD,aAAK,OAAO,QAAQ,wCAAwC,MAAM;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAqB,QAA0B;AACrD,UAAM,WAAW,GAAG,OAAO,aAAa,IAAI,OAAO,UAAU;AAC7D,SAAK,OAAO,MAAM,gDAAgD,QAAQ,EAAE;AAC5E,SAAK,iBAAiB,IAAI,QAAQ;AAClC,SAAK,KAAK,UAAU,QAAQ;AAE5B,WAAO,GAAG,SAAS,MAAM;AACvB,WAAK,iBAAiB,OAAO,QAAQ;AACrC,WAAK,KAAK,sBAAsB,QAAQ;AACxC,WAAK,OAAO,MAAM,mDAAmD,QAAQ,EAAE;AAAA,IACjF,CAAC;AAED,WAAO,GAAG,SAAS,CAAC,UAAU;AAC5B,WAAK,OAAO,QAAQ,4CAA4C,KAAK;AAAA,IACvE,CAAC;AAAA,EAKH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,QAAI,CAAC,KAAK,QAAQ;AAChB;AAAA,IACF;AAEA,SAAK,SAAS;AACd,SAAK,OAAO,MAAM,0CAA0C;AAG5D,QAAI,KAAK,iBAAiB;AACxB,YAAM,KAAK,gBAAgB,KAAK;AAChC,WAAK,kBAAkB;AAAA,IACzB;AAGA,QAAI,KAAK,eAAe;AACtB,UAAI;AACF,aAAK,cAAc,OAAO,IAAI;AAC9B,aAAK,cAAc,KAAK,SAAS;AACjC,mBAAW,MAAM;AACf,cAAI;AACF,iBAAK,eAAe,KAAK,SAAS;AAAA,UACpC,QAAQ;AAAA,UAAC;AAAA,QACX,GAAG,GAAI;AAAA,MACT,QAAQ;AAAA,MAAC;AACT,WAAK,gBAAgB;AAAA,IACvB;AAGA,QAAI,KAAK,YAAY;AACnB,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,aAAK,YAAY,MAAM,MAAM,QAAQ,CAAC;AAAA,MACxC,CAAC;AACD,WAAK,aAAa;AAAA,IACpB;AAEA,SAAK,iBAAiB,MAAM;AAC5B,SAAK,KAAK,OAAO;AACjB,SAAK,OAAO,MAAM,sCAAsC;AAAA,EAC1D;AAAA;AAAA;AAAA;AAAA,EAKA,aAAqB;AACnB,WAAO,UAAU,KAAK,QAAQ,UAAU,IAAI,KAAK,QAAQ,UAAU,GAAG,KAAK,QAAQ,IAAI;AAAA,EACzF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAyB;AACvB,WAAO,KAAK,iBAAiB;AAAA,EAC/B;AACF;;;ACrPO,SAAS,wBACd,YACA,SACA,MACA,QAAgB,SAChB,SAAiB,MACA;AACjB,QAAM,UAAU,WAAW,KAAK,EAAE,QAAQ,oBAAoB,EAAE;AAChE,QAAM,QAAQ,oBAAoB,OAAO;AACzC,QAAM,YAAY,UAAU;AAC5B,MAAI,MAAM,SAAS,IAAI,WAAW;AAChC,UAAM,IAAI;AAAA,MACR,8BAA8B,MAAM,SAAS,CAAC,eAAe,SAAS;AAAA,IACxE;AAAA,EACF;AACA,QAAM,IAAI,KAAK,IAAI,OAAO,OAAO;AACjC,QAAM,IAAI,KAAK,IAAI,QAAQ,IAAI;AAC/B,QAAM,QAAQ,IAAI,MAAe,IAAI,CAAC;AACtC,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,WAAW,IAAI,UAAU;AAC/B,YAAM,UAAU,YAAY;AAC5B,YAAM,SAAS,KAAK,WAAW;AAC/B,YAAM,IAAI,IAAI,CAAC,KAAM,MAAM,OAAO,KAAK,MAAM,SAAU,IAAI,OAAO;AAAA,IACpE;AAAA,EACF;AACA,SAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,MAAM,MAAM;AACrD;AASO,SAAS,wBAAwB,OAAgC;AACtE,QAAM,QAAQ,IAAI,WAAW,KAAK,KAAM,MAAM,UAAU,MAAM,OAAQ,CAAC,CAAC;AACxE,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,aAAS,IAAI,GAAG,IAAI,MAAM,OAAO,KAAK;AACpC,YAAM,KAAK,MAAM,MAAM,IAAI,MAAM,QAAQ,CAAC;AAC1C,UAAI,CAAC,GAAI;AACT,YAAM,WAAW,IAAI,MAAM,UAAU;AACrC,YAAM,UAAU,YAAY;AAC5B,YAAM,SAAS,KAAK,WAAW;AAC/B,YAAM,OAAO,KAAK,MAAM,OAAO,KAAK,KAAM,KAAK;AAAA,IACjD;AAAA,EACF;AACA,SAAO,kBAAkB,KAAK;AAChC;AAOO,SAAS,kBACd,SACA,MACA,QAAgB,SAChB,SAAiB,MACA;AACjB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,IAAI,MAAe,QAAQ,MAAM,EAAE,KAAK,IAAI;AAAA,EACrD;AACF;AAMA,IAAM,eACJ;AAEF,SAAS,kBAAkB,OAA2B;AACpD,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,UAAM,KAAK,MAAM,CAAC,KAAK;AACvB,UAAM,KAAK,MAAM,IAAI,CAAC,KAAK;AAC3B,UAAM,KAAK,MAAM,IAAI,CAAC,KAAK;AAC3B,WAAO,aAAa,MAAM,CAAC;AAC3B,WAAO,cAAe,KAAK,MAAS,IAAM,MAAM,CAAE;AAClD,WAAO,IAAI,IAAI,MAAM,SAAS,cAAe,KAAK,OAAS,IAAM,MAAM,CAAE,IAAK;AAC9E,WAAO,IAAI,IAAI,MAAM,SAAS,aAAa,KAAK,EAAI,IAAK;AAAA,EAC3D;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,KAAyB;AACpD,QAAM,SAAS,IAAI,OAAO,KAAK,KAAK,IAAI,SAAS,CAAC,IAAI,GAAG,GAAG;AAC5D,QAAM,WAAW,OAAO,QAAQ,OAAO,EAAE;AACzC,QAAM,MAAM,IAAI,WAAW,KAAK,MAAO,SAAS,SAAS,IAAK,CAAC,CAAC;AAChE,MAAI,OAAO;AACX,MAAI,QAAQ;AACZ,MAAI,SAAS;AACb,aAAW,MAAM,UAAU;AACzB,UAAM,MAAM,aAAa,QAAQ,EAAE;AACnC,QAAI,MAAM,EAAG;AACb,YAAS,SAAS,IAAK;AACvB,YAAQ;AACR,QAAI,QAAQ,GAAG;AACb,cAAQ;AACR,UAAI,QAAQ,IAAK,SAAS,OAAQ;AAAA,IACpC;AAAA,EACF;AACA,SAAO,IAAI,SAAS,GAAG,MAAM;AAC/B;","names":["ff","cleanup","http","spawn","http","spawn","i","sps","pps","spawn","spawn","created","requestedId","sps","pps","address","http","spawn","EventEmitter","EventEmitter","pts","ts","convertToAnnexB","splitAnnexBToNalPayloads","EventEmitter","spawn","http","EventEmitter","http","EventEmitter","spawn","EventEmitter","path","convertToAnnexB","EventEmitter","spawn","EventEmitter","getH265NalType","EventEmitter","EventEmitter","spawn","parseAnnexBNalUnits","EventEmitter","convertToAnnexB","spawn","EventEmitter","spawn","net"]}
|