@camstack/addon-webrtc-adaptive 0.1.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/addon.ts","../src/server.ts","../src/types.ts","../src/fanout.ts","../src/ffmpeg-source.ts","../src/nal-utils.ts","../src/h264-utils.ts","../src/adaptive-controller.ts","../src/h265-utils.ts","../src/session.ts"],"sourcesContent":["import type { ICamstackAddon, AddonManifest, AddonContext, IConfigurable, ConfigUISchema, CapabilityProviderMap } from '@camstack/types'\nimport { AdaptiveStreamServer } from './server.js'\nimport type { AdaptiveStreamServerOptions } from './server.js'\n\nexport class WebrtcAdaptiveAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'webrtc-adaptive',\n name: 'Adaptive WebRTC',\n version: '0.1.0',\n description: 'Adaptive WebRTC streaming with quality degradation/recovery',\n capabilities: [{ name: 'webrtc', mode: 'collection' }],\n }\n\n private server: AdaptiveStreamServer | null = null\n private currentConfig: AdaptiveStreamServerOptions = {}\n\n async initialize(context: AddonContext): Promise<void> {\n this.currentConfig = {\n ffmpegPath: (context.addonConfig.ffmpegPath as string) ?? 'ffmpeg',\n logger: context.logger as any,\n }\n this.server = new AdaptiveStreamServer(this.currentConfig)\n context.logger.info('WebRTC Adaptive streaming initialized')\n }\n\n async shutdown(): Promise<void> {\n // server cleanup if needed\n this.server = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null {\n if (name === 'webrtc' as string && this.server) {\n return this.server as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n getConfig(): Record<string, unknown> {\n return { ...this.currentConfig }\n }\n\n async onConfigChange(config: Record<string, unknown>): Promise<void> {\n this.currentConfig = {\n ...this.currentConfig,\n ffmpegPath: (config.ffmpegPath as string) ?? this.currentConfig.ffmpegPath,\n }\n }\n\n getServer(): AdaptiveStreamServer | null {\n return this.server\n }\n}\n\nexport default WebrtcAdaptiveAddon\n","/**\n * Adaptive WebRTC streaming server — multi-camera manager with quality adaptation.\n *\n * For each camera:\n * - Spawns AdaptiveFfmpegSource to transcode RTSP → H.264 with configurable params\n * - Uses StreamFanout to distribute frames to multiple viewer sessions\n * - AdaptiveController monitors stats and adjusts quality per-camera\n *\n * Usage:\n * const server = new AdaptiveStreamServer({ ... });\n * server.addCamera(\"front_door\", { rtspUrl: \"rtsp://...\", profiles: [...] });\n * const { sessionId, answer } = await server.handleWhepOffer(\"front_door\", sdpOffer);\n */\n\nimport crypto from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport type { FrameSource, FrameSourceFactory, Logger, MediaFrame } from \"./types.js\";\nimport { asLogger, createNullLogger } from \"./types.js\";\nimport { StreamFanout } from \"./fanout.js\";\nimport { AdaptiveFfmpegSource, type EncodingParams } from \"./ffmpeg-source.js\";\nimport {\n AdaptiveController,\n type QualityProfile,\n type QualityTier,\n type StreamStats,\n} from \"./adaptive-controller.js\";\nimport { AdaptiveSession, type SessionInfo, type SessionStats } from \"./session.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface AdaptiveStreamServerOptions {\n /** FFmpeg binary path (default: \"ffmpeg\"). */\n ffmpegPath?: string;\n /** STUN servers for ICE. */\n stunServers?: string[];\n /** TURN servers for ICE. */\n turnServers?: Array<{ urls: string; username?: string; credential?: string }>;\n /** ICE port range. */\n icePortRange?: [number, number];\n /** Additional host IPs to announce. */\n iceAdditionalHostAddresses?: string[];\n /** Logger. */\n logger?: Logger;\n}\n\nexport interface CameraConfig {\n /** Primary RTSP source URL (typically main stream). */\n rtspUrl: string;\n /** Secondary RTSP source URL (typically sub stream, for Level 3 switching). */\n subRtspUrl?: string;\n /** Quality profiles ordered from highest to lowest quality. */\n profiles: QualityProfile[];\n /** Audio mode: \"copy\" (G.711 passthrough), \"opus\" (transcode), \"off\" (no audio). Default: \"copy\". */\n audioMode?: \"copy\" | \"opus\" | \"off\";\n}\n\n/** Per-camera internal state. */\ninterface CameraState {\n config: CameraConfig;\n /** Main stream ffmpeg source + fanout. */\n mainFfmpegSource: AdaptiveFfmpegSource;\n mainFanout: StreamFanout<MediaFrame>;\n /** Sub stream ffmpeg source + fanout (created lazily on first source switch). */\n subFfmpegSource: AdaptiveFfmpegSource | null;\n subFanout: StreamFanout<MediaFrame> | null;\n /** Which source profile is currently active (\"main\" or \"sub\"). */\n activeSourceProfile: \"main\" | \"sub\";\n controller: AdaptiveController;\n sessions: Map<string, AdaptiveSession>;\n autoStopTimer: ReturnType<typeof setTimeout> | null;\n /** Guard against concurrent source switches. */\n switching: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Default quality profiles\n// ---------------------------------------------------------------------------\n\n/** Default quality profiles for adaptive streaming. */\nexport function createDefaultProfiles(): QualityProfile[] {\n return [\n {\n tier: \"high\",\n encoding: { maxBitrateKbps: 6000, width: 0, height: 0 }, // native resolution\n sourceProfile: \"main\",\n },\n {\n tier: \"medium\",\n encoding: { maxBitrateKbps: 2500, width: 1280, height: 720 },\n sourceProfile: \"main\",\n },\n {\n tier: \"low\",\n encoding: { maxBitrateKbps: 1000, width: 640, height: 360 },\n sourceProfile: \"sub\",\n },\n ];\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveStreamServer\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveStreamServer extends EventEmitter {\n private readonly ffmpegPath: string;\n private readonly stunServers: string[] | undefined;\n private readonly turnServers: AdaptiveStreamServerOptions[\"turnServers\"];\n private readonly icePortRange: [number, number] | undefined;\n private readonly iceAdditionalHostAddresses: string[] | undefined;\n private readonly logger: Logger;\n\n private readonly cameras = new Map<string, CameraState>();\n private readonly sessionCamera = new Map<string, string>();\n private stopped = false;\n\n constructor(options: AdaptiveStreamServerOptions = {}) {\n super();\n this.ffmpegPath = options.ffmpegPath ?? \"ffmpeg\";\n this.stunServers = options.stunServers;\n this.turnServers = options.turnServers;\n this.icePortRange = options.icePortRange;\n this.iceAdditionalHostAddresses = options.iceAdditionalHostAddresses;\n this.logger = options.logger ? asLogger(options.logger) : createNullLogger();\n this.logger.info(\"[adaptive-server] Initialized\");\n }\n\n // -----------------------------------------------------------------------\n // Camera management\n // -----------------------------------------------------------------------\n\n /** Register a camera with adaptive streaming. */\n addCamera(name: string, config: CameraConfig): void {\n if (this.cameras.has(name)) {\n this.logger.warn(`[adaptive-server] Camera \"${name}\" already registered`);\n return;\n }\n\n const profiles = config.profiles;\n const initialParams = profiles[0]!.encoding;\n\n const mainFfmpegSource = new AdaptiveFfmpegSource({\n rtspUrl: config.rtspUrl,\n initialParams,\n audioMode: config.audioMode ?? \"copy\",\n ffmpegPath: this.ffmpegPath,\n logger: this.logger,\n label: `ffmpeg:${name}:main`,\n });\n\n // Create fanout from main ffmpeg source\n const mainFanout = new StreamFanout<MediaFrame>({\n maxQueueItems: 30,\n createSource: () => mainFfmpegSource.source,\n onError: (err) => {\n this.logger.error(`[adaptive-server] Main fanout error (${name}):`, err);\n },\n });\n\n // Create adaptive controller\n const controller = new AdaptiveController({\n profiles,\n onQualityChange: async (from, to) => {\n await this.handleQualityChange(name, from, to);\n },\n logger: this.logger,\n });\n\n this.cameras.set(name, {\n config,\n mainFfmpegSource,\n mainFanout,\n subFfmpegSource: null,\n subFanout: null,\n activeSourceProfile: \"main\",\n controller,\n sessions: new Map(),\n autoStopTimer: null,\n switching: false,\n });\n\n this.logger.info(`[adaptive-server] Camera \"${name}\" added`);\n }\n\n /** Remove a camera and close all its sessions. */\n async removeCamera(name: string): Promise<void> {\n const cam = this.cameras.get(name);\n if (!cam) return;\n\n // Close all sessions\n const closePs: Promise<void>[] = [];\n for (const [sid, session] of cam.sessions) {\n this.sessionCamera.delete(sid);\n closePs.push(session.close().catch(() => {}));\n }\n await Promise.all(closePs);\n cam.sessions.clear();\n\n // Stop main resources\n await cam.mainFanout.stop();\n await cam.mainFfmpegSource.stop();\n\n // Stop sub resources if they exist\n if (cam.subFanout) await cam.subFanout.stop();\n if (cam.subFfmpegSource) await cam.subFfmpegSource.stop();\n\n if (cam.autoStopTimer) {\n clearTimeout(cam.autoStopTimer);\n cam.autoStopTimer = null;\n }\n\n this.cameras.delete(name);\n this.logger.info(`[adaptive-server] Camera \"${name}\" removed`);\n }\n\n getCameraNames(): string[] {\n return [...this.cameras.keys()];\n }\n\n // -----------------------------------------------------------------------\n // Signaling (2-step: server creates offer, client sends answer)\n // -----------------------------------------------------------------------\n\n /**\n * Create an adaptive session for a camera.\n * Returns a server-generated SDP offer that the client must answer.\n *\n * Flow: createSession() → server offer → client sets remote, creates answer → handleAnswer()\n */\n async createSession(\n cameraName: string,\n ): Promise<{ sessionId: string; sdpOffer: string }> {\n if (this.stopped) throw new Error(\"Server stopped\");\n\n const cam = this.cameras.get(cameraName);\n if (!cam) throw new Error(`Camera not found: ${cameraName}`);\n\n if (cam.autoStopTimer) {\n clearTimeout(cam.autoStopTimer);\n cam.autoStopTimer = null;\n }\n\n this.ensureCameraRunning(cameraName, cam);\n\n const sessionId = crypto.randomUUID();\n const activeFanout = this.getActiveFanout(cam);\n const source = activeFanout.subscribe(sessionId);\n\n const session = new AdaptiveSession({\n sessionId,\n source,\n iceConfig: {\n stunServers: this.stunServers,\n turnServers: this.turnServers,\n portRange: this.icePortRange,\n additionalHostAddresses: this.iceAdditionalHostAddresses,\n },\n onStats: (stats: SessionStats) => {\n cam.controller.reportStats(sessionId, {\n packetLoss: stats.packetLoss,\n jitterMs: stats.jitterMs,\n rttMs: stats.rttMs,\n timestamp: stats.timestamp,\n });\n this.emit(\"session:stats\", { camera: cameraName, ...stats });\n },\n logger: this.logger,\n });\n\n cam.sessions.set(sessionId, session);\n this.sessionCamera.set(sessionId, cameraName);\n\n try {\n const offer = await session.createOffer();\n this.emit(\"session:created\", { sessionId, camera: cameraName });\n return { sessionId, sdpOffer: offer.sdp };\n } catch (err) {\n cam.sessions.delete(sessionId);\n this.sessionCamera.delete(sessionId);\n activeFanout.unsubscribe(sessionId);\n await session.close().catch(() => {});\n this.scheduleCameraAutoStop(cameraName, cam);\n throw err;\n }\n }\n\n /**\n * Handle the client's SDP answer for an adaptive session.\n * Call after createSession() with the client's answer.\n */\n async handleAnswer(sessionId: string, sdpAnswer: string): Promise<void> {\n const camName = this.sessionCamera.get(sessionId);\n if (!camName) throw new Error(`Session not found: ${sessionId}`);\n\n const cam = this.cameras.get(camName);\n if (!cam) throw new Error(`Camera not found: ${camName}`);\n\n const session = cam.sessions.get(sessionId);\n if (!session) throw new Error(`Session not found: ${sessionId}`);\n\n await session.handleAnswer({ sdp: sdpAnswer, type: \"answer\" });\n }\n\n /**\n * Convenience: handleWhepOffer is NOT supported — werift requires server-initiated offers.\n * Use createSession() + handleAnswer() instead.\n */\n async handleWhepOffer(\n _cameraName: string,\n _sdpOffer: string,\n ): Promise<{ sessionId: string; sdpAnswer: string }> {\n throw new Error(\n \"handleWhepOffer is not supported — werift requires server-initiated offers. \" +\n \"Use createSession() to get a server offer, then handleAnswer() with the client's answer.\",\n );\n }\n\n // -----------------------------------------------------------------------\n // Connection pool: pre-warmed sessions without camera assignment\n // -----------------------------------------------------------------------\n\n /** Pooled sessions: sessionId → true (idle, no camera attached). */\n private readonly pooledSessions = new Set<string>();\n\n /**\n * Create a pooled session (no camera attached yet).\n * The SDP exchange happens, ICE connects, but no ffmpeg is started.\n * Call attachCamera() later to start feeding frames.\n */\n async createPooledSession(): Promise<{ sessionId: string; sdpOffer: string }> {\n if (this.stopped) throw new Error(\"Server stopped\");\n\n const sessionId = crypto.randomUUID();\n\n // Create a session with an empty source (never yields frames)\n const emptySource: FrameSource = (async function* () {\n // Idle: will be replaced when a camera is attached\n await new Promise(() => {}); // Suspend forever\n })();\n\n const session = new AdaptiveSession({\n sessionId,\n source: emptySource,\n iceConfig: {\n stunServers: this.stunServers,\n turnServers: this.turnServers,\n portRange: this.icePortRange,\n additionalHostAddresses: this.iceAdditionalHostAddresses,\n },\n logger: this.logger,\n });\n\n // Store without camera association\n this.pooledSessions.add(sessionId);\n\n // We need a temp camera state to store the session — use a virtual pool camera\n const poolCamKey = \"__pool__\";\n if (!this.cameras.has(poolCamKey)) {\n // Create a virtual camera for pool management\n const dummyFfmpeg = new AdaptiveFfmpegSource({\n rtspUrl: \"rtsp://0.0.0.0/dummy\",\n initialParams: { maxBitrateKbps: 0, width: 0, height: 0 },\n });\n const dummyFanout = new StreamFanout<MediaFrame>({\n maxQueueItems: 1,\n createSource: () => dummyFfmpeg.source,\n });\n const dummyController = new AdaptiveController({\n profiles: createDefaultProfiles(),\n onQualityChange: async () => {},\n });\n this.cameras.set(poolCamKey, {\n config: { rtspUrl: \"\", profiles: createDefaultProfiles() },\n mainFfmpegSource: dummyFfmpeg,\n mainFanout: dummyFanout,\n subFfmpegSource: null,\n subFanout: null,\n activeSourceProfile: \"main\",\n controller: dummyController,\n sessions: new Map(),\n autoStopTimer: null,\n switching: false,\n });\n }\n\n const poolCam = this.cameras.get(poolCamKey)!;\n poolCam.sessions.set(sessionId, session);\n this.sessionCamera.set(sessionId, poolCamKey);\n\n try {\n const offer = await session.createOffer();\n this.logger.info(`[adaptive-server] Pooled session ${sessionId.slice(0, 8)} created`);\n return { sessionId, sdpOffer: offer.sdp };\n } catch (err) {\n poolCam.sessions.delete(sessionId);\n this.sessionCamera.delete(sessionId);\n this.pooledSessions.delete(sessionId);\n await session.close().catch(() => {});\n throw err;\n }\n }\n\n /**\n * Attach a camera to a pooled session.\n * Starts the ffmpeg transcoder and begins feeding frames.\n */\n async attachCamera(sessionId: string, cameraName: string): Promise<void> {\n if (!this.pooledSessions.has(sessionId)) {\n throw new Error(`Session ${sessionId} is not a pooled session`);\n }\n\n const cam = this.cameras.get(cameraName);\n if (!cam) throw new Error(`Camera not found: ${cameraName}`);\n\n // Ensure camera is running\n this.ensureCameraRunning(cameraName, cam);\n\n // Move session from pool to camera\n const poolCam = this.cameras.get(\"__pool__\");\n const session = poolCam?.sessions.get(sessionId);\n if (!session) throw new Error(`Pooled session not found: ${sessionId}`);\n\n poolCam!.sessions.delete(sessionId);\n cam.sessions.set(sessionId, session);\n this.sessionCamera.set(sessionId, cameraName);\n this.pooledSessions.delete(sessionId);\n\n // Subscribe to camera fanout and start feeding\n const activeFanout = this.getActiveFanout(cam);\n const source = activeFanout.subscribe(sessionId);\n session.replaceSource(source);\n\n this.logger.info(`[adaptive-server] Attached camera \"${cameraName}\" to session ${sessionId.slice(0, 8)}`);\n }\n\n /**\n * Detach a camera from a session (session returns to pool).\n */\n async detachCamera(sessionId: string): Promise<void> {\n const camName = this.sessionCamera.get(sessionId);\n if (!camName || camName === \"__pool__\") return;\n\n const cam = this.cameras.get(camName);\n if (!cam) return;\n\n const session = cam.sessions.get(sessionId);\n if (!session) return;\n\n // Stop feeding\n session.detachSource();\n\n // Unsubscribe from fanout\n const activeFanout = this.getActiveFanout(cam);\n activeFanout.unsubscribe(sessionId);\n\n // Move back to pool\n cam.sessions.delete(sessionId);\n const poolCam = this.cameras.get(\"__pool__\");\n if (poolCam) {\n poolCam.sessions.set(sessionId, session);\n this.sessionCamera.set(sessionId, \"__pool__\");\n this.pooledSessions.add(sessionId);\n }\n\n this.logger.info(`[adaptive-server] Detached camera \"${camName}\" from session ${sessionId.slice(0, 8)} (back to pool)`);\n this.scheduleCameraAutoStop(camName, cam);\n }\n\n /** Check if a session is in the idle pool. */\n isPooledSession(sessionId: string): boolean {\n return this.pooledSessions.has(sessionId);\n }\n\n /** Set debug flag on all sessions for a camera. */\n setDebug(cameraName: string, debug: boolean): number {\n const cam = this.cameras.get(cameraName);\n if (!cam) return 0;\n let count = 0;\n for (const session of cam.sessions.values()) {\n (session as any).debug = debug;\n count++;\n }\n return count;\n }\n\n /** Get count of idle pooled sessions. */\n getPoolSize(): number {\n return this.pooledSessions.size;\n }\n\n // -----------------------------------------------------------------------\n // Session management\n // -----------------------------------------------------------------------\n\n /** Close a specific session. */\n async closeSession(sessionId: string): Promise<void> {\n const camName = this.sessionCamera.get(sessionId);\n if (!camName) return;\n\n const cam = this.cameras.get(camName);\n if (!cam) return;\n\n const session = cam.sessions.get(sessionId);\n if (!session) return;\n\n cam.sessions.delete(sessionId);\n this.sessionCamera.delete(sessionId);\n // Unsubscribe from whichever fanout is active\n const activeFanout = this.getActiveFanout(cam);\n activeFanout.unsubscribe(sessionId);\n cam.controller.removeSession(sessionId);\n await session.close();\n\n this.logger.info(`[adaptive-server] Session ${sessionId} closed (camera \"${camName}\", remaining: ${cam.sessions.size})`);\n this.emit(\"session:closed\", { sessionId, camera: camName });\n this.scheduleCameraAutoStop(camName, cam);\n }\n\n /**\n * Report client-side stats for a session (supplements RTCP monitoring).\n * Call from tRPC route when the client pushes stats.\n */\n reportClientStats(sessionId: string, stats: StreamStats): {\n currentTier: QualityTier;\n currentBitrateKbps: number;\n currentResolution: { width: number; height: number };\n sourceProfile: \"main\" | \"sub\";\n } | null {\n const camName = this.sessionCamera.get(sessionId);\n if (!camName) return null;\n\n const cam = this.cameras.get(camName);\n if (!cam) return null;\n\n cam.controller.reportStats(sessionId, stats);\n\n const profile = cam.controller.currentProfile;\n return {\n currentTier: profile.tier,\n currentBitrateKbps: profile.encoding.maxBitrateKbps,\n currentResolution: { width: profile.encoding.width, height: profile.encoding.height },\n sourceProfile: cam.activeSourceProfile,\n };\n }\n\n /** Force quality for a camera (null = auto). */\n forceQuality(cameraName: string, tier: QualityTier | null): boolean {\n const cam = this.cameras.get(cameraName);\n if (!cam) return false;\n cam.controller.forceQuality(tier);\n return true;\n }\n\n /** Get current quality info for a camera. */\n getCameraQuality(cameraName: string): {\n tier: QualityTier;\n encoding: EncodingParams;\n isAuto: boolean;\n stats: { packetLoss: number; jitterMs: number; rttMs: number };\n sessionCount: number;\n sourceProfile: \"main\" | \"sub\";\n } | null {\n const cam = this.cameras.get(cameraName);\n if (!cam) return null;\n\n const profile = cam.controller.currentProfile;\n return {\n tier: profile.tier,\n encoding: profile.encoding,\n isAuto: cam.controller.isAuto,\n stats: cam.controller.getAggregatedStats(),\n sessionCount: cam.sessions.size,\n sourceProfile: cam.activeSourceProfile,\n };\n }\n\n /** Get all sessions. */\n getSessions(cameraName?: string): SessionInfo[] {\n const infos: SessionInfo[] = [];\n if (cameraName) {\n const cam = this.cameras.get(cameraName);\n if (cam) {\n for (const s of cam.sessions.values()) infos.push(s.getInfo());\n }\n } else {\n for (const cam of this.cameras.values()) {\n for (const s of cam.sessions.values()) infos.push(s.getInfo());\n }\n }\n return infos;\n }\n\n getSessionCount(cameraName?: string): number {\n if (cameraName) return this.cameras.get(cameraName)?.sessions.size ?? 0;\n let total = 0;\n for (const cam of this.cameras.values()) total += cam.sessions.size;\n return total;\n }\n\n /** Stop all cameras and sessions. */\n async stop(): Promise<void> {\n if (this.stopped) return;\n this.stopped = true;\n\n const closePs: Promise<void>[] = [];\n for (const [name, cam] of this.cameras) {\n if (cam.autoStopTimer) {\n clearTimeout(cam.autoStopTimer);\n cam.autoStopTimer = null;\n }\n for (const [sid, session] of cam.sessions) {\n this.sessionCamera.delete(sid);\n closePs.push(session.close().catch(() => {}));\n }\n cam.sessions.clear();\n closePs.push(cam.mainFanout.stop().catch(() => {}));\n closePs.push(cam.mainFfmpegSource.stop().catch(() => {}));\n if (cam.subFanout) closePs.push(cam.subFanout.stop().catch(() => {}));\n if (cam.subFfmpegSource) closePs.push(cam.subFfmpegSource.stop().catch(() => {}));\n }\n await Promise.all(closePs);\n this.cameras.clear();\n this.logger.info(\"[adaptive-server] Stopped\");\n this.emit(\"stopped\");\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n /** Get the currently active fanout for a camera. */\n private getActiveFanout(cam: CameraState): StreamFanout<MediaFrame> {\n if (cam.activeSourceProfile === \"sub\" && cam.subFanout) {\n return cam.subFanout;\n }\n return cam.mainFanout;\n }\n\n private ensureCameraRunning(name: string, cam: CameraState): void {\n const activeFanout = this.getActiveFanout(cam);\n if (activeFanout.isRunning()) return;\n\n this.logger.info(`[adaptive-server] Starting camera \"${name}\" (${cam.activeSourceProfile})`);\n if (cam.activeSourceProfile === \"sub\" && cam.subFfmpegSource) {\n void cam.subFfmpegSource.start();\n cam.subFanout!.start();\n } else {\n void cam.mainFfmpegSource.start();\n cam.mainFanout.start();\n }\n }\n\n private scheduleCameraAutoStop(name: string, cam: CameraState): void {\n if (cam.sessions.size > 0 || this.stopped) return;\n\n if (cam.autoStopTimer) clearTimeout(cam.autoStopTimer);\n\n cam.autoStopTimer = setTimeout(async () => {\n cam.autoStopTimer = null;\n if (cam.sessions.size > 0 || this.stopped) return;\n\n this.logger.info(`[adaptive-server] No viewers for \"${name}\", stopping ffmpeg`);\n await cam.mainFanout.stop();\n await cam.mainFfmpegSource.stop();\n if (cam.subFanout) await cam.subFanout.stop();\n if (cam.subFfmpegSource) await cam.subFfmpegSource.stop();\n }, 10_000); // 10s grace period\n }\n\n // -----------------------------------------------------------------------\n // Source switching (Phase 5)\n // -----------------------------------------------------------------------\n\n /**\n * Handle a quality change from the AdaptiveController.\n * When the sourceProfile changes (main ↔ sub), performs a seamless source\n * switch for all active sessions. When only encoding params change (same\n * sourceProfile), updates ffmpeg params in-place.\n */\n private async handleQualityChange(\n cameraName: string,\n from: QualityProfile,\n to: QualityProfile,\n ): Promise<void> {\n const cam = this.cameras.get(cameraName);\n if (!cam) return;\n\n const sourceChanged = from.sourceProfile !== to.sourceProfile;\n\n if (sourceChanged) {\n await this.switchSource(cameraName, cam, to);\n } else {\n // Same source profile — just update encoding params on the active ffmpeg\n const activeSource = cam.activeSourceProfile === \"sub\"\n ? cam.subFfmpegSource\n : cam.mainFfmpegSource;\n if (activeSource) {\n await activeSource.updateParams(to.encoding);\n }\n }\n\n this.emit(\"quality:change\", {\n camera: cameraName,\n tier: to.tier,\n encoding: to.encoding,\n sourceProfile: to.sourceProfile,\n });\n }\n\n /**\n * Switch all active sessions from one source to another (main ↔ sub).\n *\n * Steps:\n * 1. Create/start the target ffmpeg source + fanout\n * 2. For each session: subscribe to new fanout, call replaceSource()\n * 3. Unsubscribe all from old fanout\n * 4. Stop old ffmpeg + fanout (save resources)\n * 5. Update activeSourceProfile\n */\n private async switchSource(\n cameraName: string,\n cam: CameraState,\n toProfile: QualityProfile,\n ): Promise<void> {\n if (cam.switching) {\n this.logger.warn(`[adaptive-server] Source switch already in progress for \"${cameraName}\", skipping`);\n return;\n }\n cam.switching = true;\n\n const switchingToSub = toProfile.sourceProfile === \"sub\";\n this.logger.info(\n `[adaptive-server] Source switch for \"${cameraName}\": ` +\n `${cam.activeSourceProfile} → ${toProfile.sourceProfile}`,\n );\n\n try {\n if (switchingToSub) {\n // Switching main → sub\n if (!cam.config.subRtspUrl) {\n this.logger.warn(\n `[adaptive-server] No subRtspUrl configured for \"${cameraName}\", ` +\n `cannot switch to sub stream — falling back to param update only`,\n );\n await cam.mainFfmpegSource.updateParams(toProfile.encoding);\n return;\n }\n\n // Lazily create sub ffmpeg source + fanout\n if (!cam.subFfmpegSource) {\n cam.subFfmpegSource = new AdaptiveFfmpegSource({\n rtspUrl: cam.config.subRtspUrl,\n initialParams: toProfile.encoding,\n ffmpegPath: this.ffmpegPath,\n logger: this.logger,\n label: `ffmpeg:${cameraName}:sub`,\n });\n\n cam.subFanout = new StreamFanout<MediaFrame>({\n maxQueueItems: 30,\n createSource: () => cam.subFfmpegSource!.source,\n onError: (err) => {\n this.logger.error(`[adaptive-server] Sub fanout error (${cameraName}):`, err);\n },\n });\n }\n\n // Start sub ffmpeg + fanout\n void cam.subFfmpegSource.start();\n cam.subFanout!.start();\n\n // Switch each session from main fanout to sub fanout\n for (const [sid, session] of cam.sessions) {\n cam.mainFanout.unsubscribe(sid);\n const newSource = cam.subFanout!.subscribe(sid);\n session.replaceSource(newSource);\n }\n\n // Stop main ffmpeg + fanout (save resources)\n await cam.mainFanout.stop();\n await cam.mainFfmpegSource.stop();\n\n cam.activeSourceProfile = \"sub\";\n } else {\n // Switching sub → main\n // Recreate main ffmpeg source (it was stopped)\n cam.mainFfmpegSource = new AdaptiveFfmpegSource({\n rtspUrl: cam.config.rtspUrl,\n initialParams: toProfile.encoding,\n ffmpegPath: this.ffmpegPath,\n logger: this.logger,\n label: `ffmpeg:${cameraName}:main`,\n });\n\n cam.mainFanout = new StreamFanout<MediaFrame>({\n maxQueueItems: 30,\n createSource: () => cam.mainFfmpegSource.source,\n onError: (err) => {\n this.logger.error(`[adaptive-server] Main fanout error (${cameraName}):`, err);\n },\n });\n\n // Start main ffmpeg + fanout\n void cam.mainFfmpegSource.start();\n cam.mainFanout.start();\n\n // Switch each session from sub fanout to main fanout\n for (const [sid, session] of cam.sessions) {\n if (cam.subFanout) cam.subFanout.unsubscribe(sid);\n const newSource = cam.mainFanout.subscribe(sid);\n session.replaceSource(newSource);\n }\n\n // Stop sub ffmpeg + fanout\n if (cam.subFanout) await cam.subFanout.stop();\n if (cam.subFfmpegSource) await cam.subFfmpegSource.stop();\n cam.subFfmpegSource = null;\n cam.subFanout = null;\n\n cam.activeSourceProfile = \"main\";\n }\n\n this.logger.info(\n `[adaptive-server] Source switch complete for \"${cameraName}\": ` +\n `now on ${cam.activeSourceProfile} stream`,\n );\n } catch (err) {\n this.logger.error(`[adaptive-server] Source switch failed for \"${cameraName}\":`, err);\n } finally {\n cam.switching = false;\n }\n }\n}\n","/**\n * Core types for media stream servers.\n * Protocol-agnostic frame types used across all server implementations.\n */\n\nexport type VideoCodec = \"H264\" | \"H265\";\nexport type AudioCodec = \"Aac\" | \"Adpcm\" | \"Opus\" | \"Pcmu\" | \"Pcma\";\n\n/**\n * A single video frame in Annex-B format (with start codes).\n */\nexport interface VideoFrame {\n /** Raw video data in Annex-B format (NAL units with 0x00000001 start codes) */\n data: Buffer;\n /** Video codec type */\n codec: VideoCodec;\n /** True if this is a keyframe (IDR for H.264, IRAP for H.265) */\n isKeyframe: boolean;\n /** Presentation timestamp in microseconds (monotonic) */\n timestampMicros: number;\n /** Optional: frame width in pixels */\n width?: number;\n /** Optional: frame height in pixels */\n height?: number;\n}\n\n/**\n * A single audio frame.\n * For AAC: data should include ADTS headers.\n * For other codecs: raw audio samples.\n */\nexport interface AudioFrame {\n /** Raw audio data */\n data: Buffer;\n /** Audio codec */\n codec: AudioCodec;\n /** Sample rate in Hz */\n sampleRate: number;\n /** Number of channels */\n channels: number;\n /** Presentation timestamp in microseconds */\n timestampMicros: number;\n}\n\n/**\n * Tagged union for video/audio frames.\n */\nexport type MediaFrame =\n | { type: \"video\"; frame: VideoFrame }\n | { type: \"audio\"; frame: AudioFrame };\n\n/**\n * Primary frame source interface.\n * Yields MediaFrame objects. Returning from the generator signals end of stream.\n * Throwing signals an error that should trigger reconnection.\n */\nexport type FrameSource = AsyncGenerator<MediaFrame, void, unknown>;\n\n/**\n * Factory that creates a new FrameSource.\n * Called each time the server needs to start/restart the stream.\n * The server is responsible for calling return() to stop the source.\n */\nexport type FrameSourceFactory = () => FrameSource;\n\n/**\n * Minimal logger interface compatible with console.\n */\nexport interface Logger {\n log(message?: unknown, ...args: unknown[]): void;\n info(message?: unknown, ...args: unknown[]): void;\n warn(message?: unknown, ...args: unknown[]): void;\n error(message?: unknown, ...args: unknown[]): void;\n debug(message?: unknown, ...args: unknown[]): void;\n}\n\n/**\n * Accept Console or partial Logger implementations.\n */\nexport type LoggerLike = Partial<Logger> | Console;\n\n/**\n * Wrap a LoggerLike into a full Logger (no-op for missing methods).\n */\nexport function asLogger(logger?: LoggerLike): Logger {\n if (!logger) return createNullLogger();\n const noop = () => {};\n return {\n log: (logger as Logger).log?.bind(logger) ?? noop,\n info: (logger as Logger).info?.bind(logger) ?? noop,\n warn: (logger as Logger).warn?.bind(logger) ?? noop,\n error: (logger as Logger).error?.bind(logger) ?? noop,\n debug: (logger as Logger).debug?.bind(logger) ?? noop,\n };\n}\n\n/**\n * Create a logger that discards all output.\n */\nexport function createNullLogger(): Logger {\n const noop = () => {};\n return { log: noop, info: noop, warn: noop, error: noop, debug: noop };\n}\n","/**\n * Stream fan-out utilities.\n * Distribute a single source stream to multiple consumers with bounded queues.\n */\n\n/**\n * Async bounded queue with push/pull interface.\n * When the queue is full, the oldest items are dropped (tail-drop).\n */\nexport class AsyncBoundedQueue<T> {\n private readonly maxItems: number;\n private readonly queue: T[] = [];\n private waiting:\n | { resolve: (r: IteratorResult<T>) => void }\n | undefined;\n private closed = false;\n\n constructor(maxItems: number) {\n this.maxItems = Math.max(1, maxItems | 0);\n }\n\n push(item: T): void {\n if (this.closed) return;\n if (this.waiting) {\n const { resolve } = this.waiting;\n this.waiting = undefined;\n resolve({ value: item, done: false });\n return;\n }\n this.queue.push(item);\n if (this.queue.length > this.maxItems) {\n this.queue.splice(0, this.queue.length - this.maxItems);\n }\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n if (this.waiting) {\n const { resolve } = this.waiting;\n this.waiting = undefined;\n resolve({ value: undefined as never, done: true });\n }\n }\n\n async next(): Promise<IteratorResult<T>> {\n // Drain buffered items even after close\n const item = this.queue.shift();\n if (item !== undefined) return { value: item, done: false };\n if (this.closed) return { value: undefined as never, done: true };\n return await new Promise<IteratorResult<T>>((resolve) => {\n this.waiting = { resolve };\n });\n }\n\n isClosed(): boolean {\n return this.closed;\n }\n\n size(): number {\n return this.queue.length;\n }\n}\n\nexport interface StreamFanoutOptions<T> {\n /** Maximum items per subscriber queue before tail-drop */\n maxQueueItems: number;\n /** Factory to create the source stream */\n createSource: () => AsyncGenerator<T, void, unknown>;\n /** Optional callback for each frame (e.g. for extracting metadata) */\n onFrame?: (frame: T) => void;\n /** Optional error handler */\n onError?: (error: unknown) => void;\n}\n\n/**\n * Fan-out a single async generator source to multiple subscribers.\n * The source stream is started when start() is called and frames are\n * distributed to all active subscriber queues.\n */\nexport class StreamFanout<T> {\n private readonly opts: StreamFanoutOptions<T>;\n private readonly queues = new Map<string, AsyncBoundedQueue<T>>();\n private source: AsyncGenerator<T, void, unknown> | null = null;\n private running = false;\n private pumpPromise: Promise<void> | null = null;\n\n constructor(opts: StreamFanoutOptions<T>) {\n this.opts = opts;\n }\n\n /** Start pumping frames from the source to all subscribers. */\n start(): void {\n if (this.running) return;\n this.running = true;\n this.source = this.opts.createSource();\n\n this.pumpPromise = (async () => {\n try {\n for await (const frame of this.source!) {\n try {\n this.opts.onFrame?.(frame);\n } catch {\n // ignore observer errors\n }\n for (const q of this.queues.values()) {\n q.push(frame);\n }\n }\n } catch (e) {\n this.opts.onError?.(e);\n } finally {\n this.running = false;\n for (const q of this.queues.values()) q.close();\n this.queues.clear();\n }\n })();\n }\n\n /**\n * Create a subscriber async generator.\n * Returns an async generator that yields frames from the shared source.\n * The generator terminates when the source ends or unsubscribe is called.\n */\n subscribe(id: string): AsyncGenerator<T, void, unknown> {\n const q = new AsyncBoundedQueue<T>(this.opts.maxQueueItems);\n if (!this.running) {\n // Pump already finished — close immediately so the subscriber terminates\n q.close();\n } else {\n this.queues.set(id, q);\n }\n const self = this;\n return (async function* () {\n try {\n while (true) {\n const r = await q.next();\n if (r.done) return;\n yield r.value;\n }\n } finally {\n q.close();\n self.queues.delete(id);\n }\n })();\n }\n\n /** Unsubscribe a specific subscriber. */\n unsubscribe(id: string): void {\n const q = this.queues.get(id);\n if (q) {\n q.close();\n this.queues.delete(id);\n }\n }\n\n /** Stop the source and close all subscriber queues. */\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n const src = this.source;\n this.source = null;\n for (const q of this.queues.values()) q.close();\n this.queues.clear();\n\n // Best-effort source termination with timeout to avoid hanging\n // if the source generator is stuck in an uninterruptible await\n const STOP_TIMEOUT = 3_000;\n const timeout = new Promise<void>((r) => setTimeout(r, STOP_TIMEOUT));\n try {\n await Promise.race([\n (async () => {\n try {\n await src?.return(undefined as never);\n } catch {\n // ignore\n }\n try {\n await this.pumpPromise;\n } catch {\n // ignore\n }\n })(),\n timeout,\n ]);\n } catch {\n // ignore\n }\n this.pumpPromise = null;\n }\n\n /** Returns true if the fan-out is running. */\n isRunning(): boolean {\n return this.running;\n }\n\n /** Returns the number of active subscribers. */\n subscriberCount(): number {\n return this.queues.size;\n }\n}\n","/**\n * Adaptive FFmpeg source — RTSP→FrameSource pipeline with hot-swappable encoding params.\n *\n * Spawns ffmpeg to transcode an RTSP source to raw H.264 Annex-B on stdout.\n * Supports dynamic bitrate/resolution changes by restarting ffmpeg with new params,\n * gating the transition on the next keyframe to avoid visual artifacts.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport type { FrameSource, Logger, MediaFrame, VideoFrame, AudioFrame } from \"./types.js\";\nimport { isH264IdrAccessUnit } from \"./h264-utils.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface EncodingParams {\n /** Max video bitrate in kbps (e.g. 8000 for 8 Mbps). */\n maxBitrateKbps: number;\n /** Output width (0 = keep source). */\n width: number;\n /** Output height (0 = keep source). */\n height: number;\n /** H.264 preset (default: \"ultrafast\"). */\n preset?: string;\n}\n\n/** Audio mode: \"copy\" = passthrough G.711, \"opus\" = transcode to Opus, \"off\" = no audio */\nexport type AudioMode = \"copy\" | \"opus\" | \"off\";\n\nexport interface AdaptiveFfmpegSourceOptions {\n /** RTSP source URL. */\n rtspUrl: string;\n /** Initial encoding parameters. */\n initialParams: EncodingParams;\n /** Audio mode (default: \"copy\"). */\n audioMode?: AudioMode;\n /** FFmpeg binary path (default: \"ffmpeg\"). */\n ffmpegPath?: string;\n /** Logger instance. */\n logger?: Logger;\n /** Label for logging. */\n label?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Annex-B access unit splitter from raw byte stream\n// ---------------------------------------------------------------------------\n\n/**\n * Buffer incoming Annex-B data from ffmpeg stdout and yield complete access units.\n *\n * ffmpeg pipes a continuous byte stream — NAL units are split across arbitrary\n * 8KB chunks. We accumulate data and split on Access Unit Delimiters (NAL type 9),\n * which ffmpeg emits with `-x264opts aud=1`.\n *\n * Each AUD marks the start of a new access unit (= one video frame).\n */\nclass AnnexBAccessUnitAssembler {\n private buffer: Buffer = Buffer.alloc(0);\n\n /** Feed data from ffmpeg stdout. Returns complete access units (one per frame). */\n feed(data: Buffer): Buffer[] {\n this.buffer = this.buffer.length > 0\n ? Buffer.concat([this.buffer, data])\n : data;\n\n const aus: Buffer[] = [];\n\n // Find AUD boundaries (NAL type 9) — each AUD starts a new access unit\n // An AUD is: start_code + 0x09 + 1 byte\n const audPositions: number[] = [];\n for (let i = 0; i < this.buffer.length - 5; i++) {\n if (this.buffer[i] === 0 && this.buffer[i + 1] === 0) {\n let scLen = 0;\n if (this.buffer[i + 2] === 0 && this.buffer[i + 3] === 1) scLen = 4;\n else if (this.buffer[i + 2] === 1) scLen = 3;\n if (scLen > 0) {\n const nalType = this.buffer[i + scLen]! & 0x1f;\n if (nalType === 9) { // AUD\n audPositions.push(i);\n }\n }\n }\n }\n\n // Need at least 2 AUD positions to extract a complete AU (between two AUDs)\n if (audPositions.length < 2) return aus;\n\n // Extract complete AUs (between consecutive AUDs)\n for (let j = 0; j < audPositions.length - 1; j++) {\n const au = this.buffer.subarray(audPositions[j]!, audPositions[j + 1]!);\n if (au.length > 4) aus.push(au);\n }\n\n // Keep data from the last AUD onwards (incomplete AU)\n this.buffer = this.buffer.subarray(audPositions[audPositions.length - 1]!);\n\n return aus;\n }\n\n /** Flush any remaining buffered data as a final access unit. */\n flush(): Buffer | null {\n if (this.buffer.length <= 4) return null;\n const au = this.buffer;\n this.buffer = Buffer.alloc(0);\n return au;\n }\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveFfmpegSource\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveFfmpegSource {\n private readonly rtspUrl: string;\n private readonly ffmpegPath: string;\n private readonly logger: Logger | undefined;\n private readonly label: string;\n private readonly audioMode: AudioMode;\n\n private currentParams: EncodingParams;\n private proc: ChildProcess | null = null;\n private audioProc: ChildProcess | null = null;\n private closed = false;\n\n /** Push callback for the frame source. */\n private pushFrame: ((frame: MediaFrame) => void) | null = null;\n private closeSource: (() => void) | null = null;\n\n /** The FrameSource async generator. Created once, survives ffmpeg restarts. */\n readonly source: FrameSource;\n\n constructor(options: AdaptiveFfmpegSourceOptions) {\n this.rtspUrl = options.rtspUrl;\n this.ffmpegPath = options.ffmpegPath ?? \"ffmpeg\";\n this.audioMode = options.audioMode ?? \"copy\";\n this.logger = options.logger;\n this.label = options.label ?? \"adaptive-ffmpeg\";\n this.currentParams = { ...options.initialParams };\n\n // Create a push-based FrameSource that survives ffmpeg restarts\n const queue: MediaFrame[] = [];\n let resolve: ((value: IteratorResult<MediaFrame>) => void) | null = null;\n let done = false;\n\n this.pushFrame = (mf: MediaFrame) => {\n if (done) return;\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: mf, done: false });\n } else {\n queue.push(mf);\n if (queue.length > 120) queue.splice(0, queue.length - 60);\n }\n };\n\n this.closeSource = () => {\n done = true;\n if (resolve) {\n const r = resolve;\n resolve = null;\n r({ value: undefined as never, done: true });\n }\n };\n\n this.source = (async function* () {\n try {\n while (true) {\n const item = queue.shift();\n if (item) { yield item; continue; }\n if (done) return;\n const result = await new Promise<IteratorResult<MediaFrame>>((r) => {\n resolve = r;\n });\n if (result.done) return;\n yield result.value;\n }\n } finally {\n done = true;\n }\n })();\n }\n\n /** Start the ffmpeg process with current encoding params. */\n async start(): Promise<void> {\n if (this.closed) return;\n this.spawnFfmpeg();\n }\n\n /** Get the current encoding parameters. */\n getParams(): Readonly<EncodingParams> {\n return { ...this.currentParams };\n }\n\n /**\n * Hot-swap encoding parameters.\n * Stops the current ffmpeg and starts a new one with updated params.\n * The FrameSource continues seamlessly — the new ffmpeg's first keyframe\n * is gated internally so consumers see a clean transition.\n */\n async updateParams(params: Partial<EncodingParams>): Promise<void> {\n const prev = { ...this.currentParams };\n if (params.maxBitrateKbps !== undefined) this.currentParams.maxBitrateKbps = params.maxBitrateKbps;\n if (params.width !== undefined) this.currentParams.width = params.width;\n if (params.height !== undefined) this.currentParams.height = params.height;\n if (params.preset !== undefined) this.currentParams.preset = params.preset;\n\n // No change → skip restart\n if (\n prev.maxBitrateKbps === this.currentParams.maxBitrateKbps &&\n prev.width === this.currentParams.width &&\n prev.height === this.currentParams.height\n ) return;\n\n this.logger?.info(\n `[${this.label}] Updating params: ${prev.maxBitrateKbps}kbps ${prev.width}x${prev.height} → ` +\n `${this.currentParams.maxBitrateKbps}kbps ${this.currentParams.width}x${this.currentParams.height}`,\n );\n\n // Kill old ffmpeg and start new one\n await this.killFfmpeg();\n if (!this.closed) {\n this.spawnFfmpeg();\n }\n }\n\n /** Stop the source and kill ffmpeg. */\n async stop(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n await this.killFfmpeg();\n this.closeSource?.();\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n private spawnFfmpeg(): void {\n const { maxBitrateKbps, width, height, preset } = this.currentParams;\n\n const args: string[] = [\n \"-hide_banner\",\n \"-loglevel\", \"error\",\n \"-fflags\", \"+nobuffer\",\n \"-flags\", \"+low_delay\",\n \"-rtsp_transport\", \"tcp\",\n \"-i\", this.rtspUrl,\n \"-c:v\", \"libx264\",\n \"-preset\", preset ?? \"ultrafast\",\n \"-tune\", \"zerolatency\",\n \"-crf\", \"28\",\n \"-maxrate\", `${maxBitrateKbps}k`,\n \"-bufsize\", `${Math.round(maxBitrateKbps * 0.5)}k`,\n \"-g\", \"50\",\n \"-keyint_min\", \"25\",\n \"-x264opts\", \"aud=1:sliced-threads=1\",\n \"-flush_packets\", \"1\",\n ];\n\n // Scale filter\n if (width > 0 && height > 0) {\n args.push(\"-vf\", `scale=${width}:${height}`);\n }\n\n // Video-only output on stdout\n args.push(\n \"-an\",\n \"-f\", \"h264\",\n \"-\",\n );\n\n this.proc = spawn(this.ffmpegPath, args, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n this.proc.on(\"error\", (err) => {\n this.logger?.error(`[${this.label}] ffmpeg spawn error: ${err.message}`);\n });\n\n this.proc.on(\"close\", (code, signal) => {\n this.logger?.debug(`[${this.label}] ffmpeg exited code=${code} signal=${signal}`);\n this.proc = null;\n if (!this.closed) {\n setTimeout(() => {\n if (!this.closed) this.spawnFfmpeg();\n }, 2000);\n }\n });\n\n this.proc.stderr?.on(\"data\", (data: Buffer) => {\n const s = data.toString();\n if (s.includes(\"error\") || s.includes(\"Error\") || s.includes(\"fatal\")) {\n this.logger?.error(`[${this.label}] ffmpeg: ${s.trim()}`);\n }\n });\n\n if (!this.proc.stdout) {\n this.logger?.error(`[${this.label}] ffmpeg stdout not available`);\n return;\n }\n\n // Parse raw H.264 Annex-B from stdout into access units\n const assembler = new AnnexBAccessUnitAssembler();\n const startTime = Date.now();\n\n this.proc.stdout.on(\"data\", (data: Buffer) => {\n if (this.closed) return;\n const aus = assembler.feed(data);\n for (const au of aus) {\n if (au.length < 4) continue;\n const isKeyframe = isH264IdrAccessUnit(au);\n const timestampMicros = (Date.now() - startTime) * 1000;\n\n const vf: VideoFrame = {\n data: au,\n codec: \"H264\",\n isKeyframe,\n timestampMicros,\n };\n\n this.pushFrame?.({ type: \"video\", frame: vf });\n }\n });\n\n this.proc.stdout.on(\"end\", () => {\n const remaining = assembler.flush();\n if (remaining && remaining.length > 4 && this.pushFrame) {\n const vf: VideoFrame = {\n data: remaining,\n codec: \"H264\",\n isKeyframe: isH264IdrAccessUnit(remaining),\n timestampMicros: (Date.now() - startTime) * 1000,\n };\n this.pushFrame({ type: \"video\", frame: vf });\n }\n });\n\n // Audio process (separate from video — survives video ffmpeg restarts)\n if (this.audioMode !== \"off\") {\n const audioArgs = [\n \"-hide_banner\", \"-loglevel\", \"error\",\n \"-fflags\", \"+nobuffer+flush_packets\",\n \"-rtsp_transport\", \"tcp\",\n \"-analyzeduration\", \"500000\",\n \"-probesize\", \"500000\",\n \"-i\", this.rtspUrl,\n \"-vn\",\n ];\n\n let audioCodecLabel: string;\n let frameSize: number;\n let sampleRate: number;\n let codecName: string;\n\n if (this.audioMode === \"opus\") {\n // Transcode to Opus — better quality, uses CPU\n audioArgs.push(\"-c:a\", \"libopus\", \"-ar\", \"48000\", \"-ac\", \"2\", \"-b:a\", \"64k\", \"-f\", \"ogg\", \"-\");\n audioCodecLabel = \"opus\";\n frameSize = 960; // 20ms at 48kHz, 2 channels, but OGG needs parsing — use raw instead\n sampleRate = 48000;\n codecName = \"Opus\";\n // Actually OGG is complex to parse. Use raw s16le + re-encode... too complex.\n // For Opus mode, just use G.711 for now and transcode later when we have a proper parser.\n // TODO: implement proper Opus transcoding\n audioArgs.length = 0;\n audioArgs.push(\n \"-hide_banner\", \"-loglevel\", \"error\",\n \"-rtsp_transport\", \"tcp\", \"-i\", this.rtspUrl,\n \"-vn\", \"-c:a\", \"pcm_mulaw\", \"-ar\", \"8000\", \"-ac\", \"1\", \"-f\", \"mulaw\", \"-\",\n );\n audioCodecLabel = \"pcmu(opus-fallback)\";\n frameSize = 160;\n sampleRate = 8000;\n codecName = \"Pcmu\";\n } else {\n // Copy mode: passthrough G.711 µ-law (zero CPU, native WebRTC support)\n audioArgs.push(\"-c:a\", \"pcm_mulaw\", \"-ar\", \"8000\", \"-ac\", \"1\", \"-f\", \"mulaw\", \"-\");\n audioCodecLabel = \"pcmu\";\n frameSize = 160; // 20ms at 8kHz\n sampleRate = 8000;\n codecName = \"Pcmu\";\n }\n\n this.audioProc = spawn(this.ffmpegPath, audioArgs, {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n this.audioProc.on(\"error\", () => {});\n this.audioProc.on(\"close\", () => { this.audioProc = null; });\n\n if (this.audioProc.stdout) {\n let audioBuf: Buffer = Buffer.alloc(0);\n this.audioProc.stdout.on(\"data\", (data: Buffer) => {\n if (this.closed || !this.pushFrame) return;\n audioBuf = audioBuf.length > 0 ? Buffer.concat([audioBuf, data]) as Buffer<ArrayBuffer> : data;\n while (audioBuf.length >= frameSize) {\n const audioFrame = audioBuf.subarray(0, frameSize);\n audioBuf = audioBuf.subarray(frameSize);\n this.pushFrame!({\n type: \"audio\",\n frame: {\n data: Buffer.from(audioFrame),\n codec: codecName as any,\n sampleRate,\n channels: 1,\n timestampMicros: (Date.now() - startTime) * 1000,\n },\n });\n }\n });\n }\n\n this.logger?.info(\n `[${this.label}] Started: ${maxBitrateKbps}kbps ` +\n (width > 0 ? `${width}x${height}` : \"native\") +\n ` +audio(${audioCodecLabel})`,\n );\n } else {\n this.logger?.info(\n `[${this.label}] Started: ${maxBitrateKbps}kbps ` +\n (width > 0 ? `${width}x${height}` : \"native\") +\n \" (no audio)\",\n );\n }\n }\n\n private async killFfmpeg(): Promise<void> {\n // Kill video process\n const proc = this.proc;\n if (proc) {\n this.proc = null;\n try { proc.kill(\"SIGTERM\"); } catch { /* */ }\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => { try { proc.kill(\"SIGKILL\"); } catch { /* */ } resolve(); }, 3000);\n proc.on(\"close\", () => { clearTimeout(timer); resolve(); });\n });\n }\n\n // Kill audio process\n const audioProc = this.audioProc;\n if (audioProc) {\n this.audioProc = null;\n try { audioProc.kill(\"SIGTERM\"); } catch { /* */ }\n await new Promise<void>((resolve) => {\n const timer = setTimeout(() => { try { audioProc.kill(\"SIGKILL\"); } catch { /* */ } resolve(); }, 1000);\n audioProc.on(\"close\", () => { clearTimeout(timer); resolve(); });\n });\n }\n }\n}\n","/**\n * NAL unit parsing utilities.\n * Protocol-agnostic functions for working with Annex-B NAL units.\n */\n\nimport type { VideoCodec } from \"./types.js\";\n\nconst NAL_START_CODE_4B = Buffer.from([0x00, 0x00, 0x00, 0x01]);\nconst NAL_START_CODE_3B = Buffer.from([0x00, 0x00, 0x01]);\n\n/**\n * Returns true if the buffer starts with an Annex-B start code (0x00000001 or 0x000001).\n */\nexport function hasStartCodes(data: Buffer): boolean {\n if (data.length < 4) return false;\n if (data.subarray(0, 4).equals(NAL_START_CODE_4B)) return true;\n if (data.subarray(0, 3).equals(NAL_START_CODE_3B)) return true;\n return false;\n}\n\n/**\n * Split Annex-B data into individual NAL unit payloads (without start codes).\n * Works for both H.264 and H.265.\n */\nexport function splitAnnexBToNals(annexB: Buffer): Buffer[] {\n const nals: Buffer[] = [];\n const len = annexB.length;\n\n const isStartCodeAt = (i: number): number => {\n if (i + 3 <= len && annexB[i] === 0x00 && annexB[i + 1] === 0x00) {\n if (annexB[i + 2] === 0x01) return 3;\n if (i + 4 <= len && annexB[i + 2] === 0x00 && annexB[i + 3] === 0x01)\n return 4;\n }\n return 0;\n };\n\n let i = 0;\n // Find first start code\n while (i < len) {\n const sc = isStartCodeAt(i);\n if (sc) break;\n i++;\n }\n\n while (i < len) {\n const sc = isStartCodeAt(i);\n if (!sc) {\n i++;\n continue;\n }\n const nalStart = i + sc;\n let j = nalStart;\n while (j < len) {\n const sc2 = isStartCodeAt(j);\n if (sc2) break;\n j++;\n }\n if (nalStart < j) {\n const nal = annexB.subarray(nalStart, j);\n if (nal.length > 0) nals.push(nal);\n }\n i = j;\n }\n\n return nals;\n}\n\n/**\n * Prepend a 4-byte Annex-B start code to a NAL payload.\n */\nexport function prependStartCode(nal: Buffer): Buffer {\n return Buffer.concat([NAL_START_CODE_4B, nal]);\n}\n\n/**\n * Join multiple NAL payloads (without start codes) into an Annex-B access unit.\n */\nexport function joinNalsToAnnexB(\n ...nals: Array<Buffer | null | undefined>\n): Buffer | undefined {\n const present = nals.filter((n): n is Buffer => !!n && n.length > 0);\n if (!present.length) return;\n const parts: Buffer[] = [];\n for (const nal of present) {\n parts.push(NAL_START_CODE_4B, nal);\n }\n return Buffer.concat(parts);\n}\n\n/**\n * Detect the actual video codec from raw NAL data.\n * Some cameras report wrong codec (e.g. \"H264\" but send H.265 data).\n * Analyzes the NAL header to determine the real codec.\n *\n * @param data - Raw video data (either Annex-B or length-prefixed)\n * @returns Detected codec type or null if detection fails\n */\nexport function detectVideoCodecFromNal(data: Buffer): VideoCodec | null {\n if (!data || data.length < 5) return null;\n\n // Find start code (00 00 00 01 or 00 00 01)\n let nalStart = -1;\n for (let i = 0; i < Math.min(data.length - 4, 100); i++) {\n if (data[i] === 0 && data[i + 1] === 0) {\n if (data[i + 2] === 0 && data[i + 3] === 1) {\n nalStart = i + 4;\n break;\n }\n if (data[i + 2] === 1) {\n nalStart = i + 3;\n break;\n }\n }\n }\n\n // If no start code, try length-prefixed (AVCC/HVCC)\n if (nalStart < 0 && data.length >= 5) {\n const len = data.readUInt32BE(0);\n if (len > 0 && len <= data.length - 4) {\n nalStart = 4;\n }\n }\n\n if (nalStart < 0 || nalStart >= data.length) return null;\n\n const nalByte = data[nalStart];\n if (nalByte === undefined) return null;\n\n // Check H.264 FIRST because H.264 NAL bytes can be misinterpreted as H.265.\n const forbiddenBit264 = (nalByte >> 7) & 1;\n const h264Type = nalByte & 0x1f;\n\n if (forbiddenBit264 === 0 && h264Type > 0 && h264Type <= 12) {\n if (h264Type === 7 || h264Type === 8) return \"H264\"; // SPS, PPS\n if (h264Type === 5) return \"H264\"; // IDR\n if (h264Type === 1) {\n const nalRefIdc = (nalByte >> 5) & 0x03;\n if (nalRefIdc >= 1) return \"H264\";\n }\n }\n\n // H.265/HEVC detection\n if (nalStart + 1 < data.length) {\n const nalByte2 = data[nalStart + 1];\n if (nalByte2 !== undefined) {\n const forbiddenBit = (nalByte >> 7) & 1;\n const hevcType = (nalByte >> 1) & 0x3f;\n const temporalId = nalByte2 & 0x07;\n\n if (forbiddenBit === 0 && temporalId > 0 && hevcType <= 40) {\n if (hevcType === 32 || hevcType === 33 || hevcType === 34)\n return \"H265\"; // VPS, SPS, PPS\n if (hevcType === 19 || hevcType === 20 || hevcType === 21)\n return \"H265\"; // IDR, CRA\n if (hevcType <= 1 && nalByte <= 0x03) return \"H265\"; // TRAIL\n }\n }\n }\n\n return null;\n}\n\nexport { NAL_START_CODE_4B, NAL_START_CODE_3B };\n","/**\n * H.264/AVC utilities.\n * AVCC to Annex-B conversion, SPS/PPS extraction, keyframe detection, RTP depacketization.\n */\n\nimport { NAL_START_CODE_4B, hasStartCodes, splitAnnexBToNals } from \"./nal-utils.js\";\n\n// --- AVCC to Annex-B conversion helpers ---\n\nfunction tryConvertWithLengthReader(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 4 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 4;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader16(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 2 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 2;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader24(\n data: Buffer,\n endian: \"be\" | \"le\",\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n const readLen24 = (buf: Buffer, at: number): number => {\n if (at + 3 > buf.length) return 0;\n const b0 = buf[at]!;\n const b1 = buf[at + 1]!;\n const b2 = buf[at + 2]!;\n return endian === \"be\"\n ? ((b0 << 16) | (b1 << 8) | b2) >>> 0\n : ((b2 << 16) | (b1 << 8) | b0) >>> 0;\n };\n while (offset < data.length) {\n if (offset + 3 > data.length) return null;\n const nalLength = readLen24(data, offset);\n offset += 3;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction looksLikeSingleH264Nal(nalPayload: Buffer): boolean {\n if (nalPayload.length < 1) return false;\n const b0 = nalPayload[0];\n if (b0 === undefined) return false;\n if ((b0 & 0x80) !== 0) return false;\n const nalType = b0 & 0x1f;\n return nalType >= 1 && nalType <= 23;\n}\n\nfunction depacketizeRtpAggregationToAnnexB(payload: Buffer): Buffer | null {\n if (payload.length < 1) return null;\n const nalHeader = payload[0]!;\n const nalType = nalHeader & 0x1f;\n const out: Buffer[] = [];\n const pushNal = (nal: Buffer) => {\n if (nal.length === 0) return;\n out.push(NAL_START_CODE_4B, nal);\n };\n\n // STAP-A (24)\n if (nalType === 24) {\n let off = 1;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // STAP-B (25)\n if (nalType === 25) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // MTAP16 (26)\n if (nalType === 26) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (off + 1 + 2 > payload.length) return null;\n off += 1 + 2;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n // MTAP24 (27)\n if (nalType === 27) {\n let off = 1 + 2;\n if (off > payload.length) return null;\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (off + 1 + 3 > payload.length) return null;\n off += 1 + 3;\n if (size <= 0 || off + size > payload.length) return null;\n pushNal(payload.subarray(off, off + size));\n off += size;\n }\n return out.length ? Buffer.concat(out) : null;\n }\n\n return null;\n}\n\n// --- Public API ---\n\n/**\n * Convert H.264 data from length-prefixed (AVCC) to Annex-B (start codes).\n * If already Annex-B, returns as-is.\n */\nexport function convertH264ToAnnexB(data: Buffer): Buffer {\n if (hasStartCodes(data)) return data;\n\n // Some models prepend a small header before an Annex-B access unit.\n const sc4 = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n const sc3 = Buffer.from([0x00, 0x00, 0x01]);\n const maxScan = Math.min(64, data.length);\n const idx4 = data.subarray(0, maxScan).indexOf(sc4);\n if (idx4 > 0) return data.subarray(idx4);\n const idx3 = data.subarray(0, maxScan).indexOf(sc3);\n if (idx3 > 0) return data.subarray(idx3);\n\n // Try AVCC → AnnexB conversion (BE then LE)\n const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));\n if (be) return be;\n const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));\n if (le) return le;\n\n // 3-byte lengths\n const be24 = tryConvertWithLengthReader24(data, \"be\");\n if (be24) return be24;\n const le24 = tryConvertWithLengthReader24(data, \"le\");\n if (le24) return le24;\n\n // 2-byte lengths\n const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));\n if (be16) return be16;\n const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));\n if (le16) return le16;\n\n // RTP aggregation payload\n const agg = depacketizeRtpAggregationToAnnexB(data);\n if (agg) return agg;\n\n // Single NAL without start code\n if (looksLikeSingleH264Nal(data)) {\n return Buffer.concat([NAL_START_CODE_4B, data]);\n }\n\n return data;\n}\n\n/**\n * Check if an H.264 Annex-B access unit is a keyframe (IDR + SPS + PPS).\n */\nexport function isH264KeyframeAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasSps = false;\n let hasPps = false;\n let hasIdr = false;\n for (const nal of nals) {\n const t = (nal[0] ?? 0) & 0x1f;\n if (t === 7) hasSps = true;\n if (t === 8) hasPps = true;\n if (t === 5) hasIdr = true;\n }\n return hasIdr && hasSps && hasPps;\n}\n\n/**\n * Check if an H.264 Annex-B access unit contains an IDR slice.\n */\nexport function isH264IdrAccessUnit(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const t = (nal[0] ?? 0) & 0x1f;\n if (t === 5) return true;\n }\n return false;\n}\n\n/**\n * Extract SPS and PPS from an H.264 Annex-B access unit.\n */\nexport function extractH264ParamSets(annexB: Buffer): {\n sps?: Buffer;\n pps?: Buffer;\n profileLevelId?: string;\n} {\n const nals = splitAnnexBToNals(annexB);\n let sps: Buffer | undefined;\n let pps: Buffer | undefined;\n let profileLevelId: string | undefined;\n\n for (const nal of nals) {\n if (nal.length < 1) continue;\n const nalType = nal[0]! & 0x1f;\n if (nalType === 7) {\n sps = nal;\n if (nal.length >= 4) {\n profileLevelId = Buffer.from([nal[1]!, nal[2]!, nal[3]!]).toString(\n \"hex\",\n );\n }\n } else if (nalType === 8) {\n pps = nal;\n }\n }\n\n const out: { sps?: Buffer; pps?: Buffer; profileLevelId?: string } = {};\n if (sps) out.sps = sps;\n if (pps) out.pps = pps;\n if (profileLevelId) out.profileLevelId = profileLevelId;\n return out;\n}\n\n/**\n * H.264 RTP depacketizer (RFC 6184).\n * Handles single NAL units, STAP-A aggregation, and FU-A/FU-B fragmentation.\n * Returns complete NAL units in Annex-B format (with start codes).\n */\nexport class H264RtpDepacketizer {\n private fuNalHeader: number | null = null;\n private fuParts: Buffer[] = [];\n\n private static parseRtpPayload(packet: Buffer): Buffer | null {\n if (!packet || packet.length < 12) return null;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return null;\n const padding = (packet[0]! & 0x20) !== 0;\n const extension = (packet[0]! & 0x10) !== 0;\n const csrcCount = packet[0]! & 0x0f;\n let offset = 12 + csrcCount * 4;\n if (offset > packet.length) return null;\n if (extension) {\n if (offset + 4 > packet.length) return null;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return null;\n }\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return null;\n end = packet.length - padLen;\n if (end < offset) return null;\n }\n if (end <= offset) return null;\n return packet.subarray(offset, end);\n }\n\n reset(): void {\n this.fuNalHeader = null;\n this.fuParts = [];\n }\n\n push(payload: Buffer): Buffer[] {\n if (payload.length === 0) return [];\n\n const rtpPayload = H264RtpDepacketizer.parseRtpPayload(payload);\n if (rtpPayload) payload = rtpPayload;\n\n if (hasStartCodes(payload)) return [payload];\n\n const b0 = payload[0]!;\n if ((b0 & 0x80) !== 0) return [];\n const nalType = b0 & 0x1f;\n\n // Single NAL unit\n if (nalType >= 1 && nalType <= 23) {\n return [Buffer.concat([NAL_START_CODE_4B, payload])];\n }\n\n // STAP-A (24)\n if (nalType === 24) {\n if (payload.length < 1 + 2) return [];\n let off = 1;\n const out: Buffer[] = [];\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return [];\n const nal = payload.subarray(off, off + size);\n off += size;\n if (nal.length < 1) return [];\n if ((nal[0]! & 0x80) !== 0) return [];\n const t = nal[0]! & 0x1f;\n if (t === 0 || t >= 24) return [];\n out.push(Buffer.concat([NAL_START_CODE_4B, nal]));\n }\n return out;\n }\n\n // FU-A (28) / FU-B (29)\n if (nalType === 28 || nalType === 29) {\n if (payload.length < 2) return [];\n const fuIndicator = payload[0]!;\n const fuHeader = payload[1]!;\n const start = (fuHeader & 0x80) !== 0;\n const end = (fuHeader & 0x40) !== 0;\n const origType = fuHeader & 0x1f;\n const reconstructedHeader = (fuIndicator & 0xe0) | origType;\n\n let off = 2;\n if (nalType === 29) {\n if (payload.length < off + 2) return [];\n off += 2;\n }\n const frag = payload.subarray(off);\n\n if (start) {\n this.fuNalHeader = reconstructedHeader;\n this.fuParts = [frag];\n } else if (this.fuNalHeader != null) {\n this.fuParts.push(frag);\n } else {\n return [];\n }\n\n if (end && this.fuNalHeader != null) {\n const nal = Buffer.concat([\n Buffer.from([this.fuNalHeader]),\n ...this.fuParts,\n ]);\n this.reset();\n return [Buffer.concat([NAL_START_CODE_4B, nal])];\n }\n return [];\n }\n\n return [];\n }\n}\n","/**\n * Adaptive quality controller — state machine for bitrate/resolution/source adaptation.\n *\n * Monitors packet loss, jitter, and RTT from RTCP reports and client-reported stats.\n * Makes quality decisions using a three-tier degradation/recovery model:\n *\n * Level 1 — Reduce bitrate cap (ffmpeg -maxrate)\n * Level 2 — Downscale resolution (ffmpeg -vf scale=)\n * Level 3 — Switch source stream (main → sub via replaceTrack)\n *\n * Hysteresis prevents oscillation: degradation requires 2 consecutive bad intervals,\n * recovery requires 3 consecutive good intervals.\n */\n\nimport type { Logger } from \"./types.js\";\nimport type { EncodingParams } from \"./ffmpeg-source.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport type QualityTier = \"high\" | \"medium\" | \"low\";\n\nexport interface QualityProfile {\n tier: QualityTier;\n /** Encoding params to apply. */\n encoding: EncodingParams;\n /** Source stream profile (\"main\" | \"sub\"). */\n sourceProfile: \"main\" | \"sub\";\n}\n\nexport interface StreamStats {\n /** Packet loss ratio (0.0 – 1.0). */\n packetLoss: number;\n /** Jitter in milliseconds. */\n jitterMs: number;\n /** Round-trip time in milliseconds. */\n rttMs: number;\n /** Timestamp of this measurement. */\n timestamp: number;\n}\n\nexport interface AdaptiveControllerOptions {\n /** Available quality profiles, ordered from highest to lowest. */\n profiles: QualityProfile[];\n /** Packet loss threshold to degrade (default: 0.02 = 2%). */\n degradeThreshold?: number;\n /** Packet loss threshold to recover (default: 0.005 = 0.5%). */\n recoverThreshold?: number;\n /** Consecutive bad intervals before degradation (default: 2). */\n degradeCount?: number;\n /** Consecutive good intervals before recovery (default: 3). */\n recoverCount?: number;\n /** Callback when quality should change. */\n onQualityChange: (from: QualityProfile, to: QualityProfile) => void | Promise<void>;\n /** Logger. */\n logger?: Logger;\n}\n\n// ---------------------------------------------------------------------------\n// EWMA (Exponentially Weighted Moving Average)\n// ---------------------------------------------------------------------------\n\nclass EWMA {\n private value: number | null = null;\n private readonly alpha: number;\n\n constructor(alpha = 0.3) {\n this.alpha = alpha;\n }\n\n update(sample: number): number {\n if (this.value === null) {\n this.value = sample;\n } else {\n this.value = this.alpha * sample + (1 - this.alpha) * this.value;\n }\n return this.value;\n }\n\n get(): number {\n return this.value ?? 0;\n }\n\n reset(): void {\n this.value = null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveController\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveController {\n private readonly profiles: readonly QualityProfile[];\n private readonly degradeThreshold: number;\n private readonly recoverThreshold: number;\n private readonly degradeCount: number;\n private readonly recoverCount: number;\n private readonly onQualityChange: AdaptiveControllerOptions[\"onQualityChange\"];\n private readonly logger: Logger | undefined;\n\n private currentIndex: number;\n private consecutiveBad = 0;\n private consecutiveGood = 0;\n private switching = false;\n\n /** Smoothed stats per session (aggregated for decisions). */\n private readonly sessionStats = new Map<string, {\n loss: EWMA;\n jitter: EWMA;\n rtt: EWMA;\n lastUpdate: number;\n }>();\n\n /** Manual override tier (null = auto). */\n private forcedTier: QualityTier | null = null;\n\n constructor(options: AdaptiveControllerOptions) {\n if (options.profiles.length === 0) {\n throw new Error(\"At least one quality profile is required\");\n }\n this.profiles = options.profiles;\n this.degradeThreshold = options.degradeThreshold ?? 0.02;\n this.recoverThreshold = options.recoverThreshold ?? 0.005;\n this.degradeCount = options.degradeCount ?? 2;\n this.recoverCount = options.recoverCount ?? 3;\n this.onQualityChange = options.onQualityChange;\n this.logger = options.logger;\n this.currentIndex = 0; // Start at highest quality\n }\n\n /** Get the current quality profile. */\n get currentProfile(): QualityProfile {\n return this.profiles[this.currentIndex]!;\n }\n\n /** Get the current quality tier. */\n get currentTier(): QualityTier {\n return this.currentProfile.tier;\n }\n\n /** Get aggregated stats summary. */\n getAggregatedStats(): { packetLoss: number; jitterMs: number; rttMs: number } {\n if (this.sessionStats.size === 0) {\n return { packetLoss: 0, jitterMs: 0, rttMs: 0 };\n }\n\n let totalLoss = 0;\n let totalJitter = 0;\n let totalRtt = 0;\n let count = 0;\n\n for (const stats of this.sessionStats.values()) {\n // Skip stale sessions (>30s without update)\n if (Date.now() - stats.lastUpdate > 30_000) continue;\n totalLoss += stats.loss.get();\n totalJitter += stats.jitter.get();\n totalRtt += stats.rtt.get();\n count++;\n }\n\n if (count === 0) return { packetLoss: 0, jitterMs: 0, rttMs: 0 };\n\n return {\n packetLoss: totalLoss / count,\n jitterMs: totalJitter / count,\n rttMs: totalRtt / count,\n };\n }\n\n /**\n * Report stats from a session (RTCP or client-reported).\n * Call this periodically (e.g. every 3–5 seconds).\n */\n reportStats(sessionId: string, stats: StreamStats): void {\n let entry = this.sessionStats.get(sessionId);\n if (!entry) {\n entry = {\n loss: new EWMA(0.3),\n jitter: new EWMA(0.3),\n rtt: new EWMA(0.3),\n lastUpdate: 0,\n };\n this.sessionStats.set(sessionId, entry);\n }\n\n entry.loss.update(stats.packetLoss);\n entry.jitter.update(stats.jitterMs);\n entry.rtt.update(stats.rttMs);\n entry.lastUpdate = stats.timestamp;\n\n // Evaluate quality after stats update\n this.evaluate();\n }\n\n /** Remove a session's stats (call on session close). */\n removeSession(sessionId: string): void {\n this.sessionStats.delete(sessionId);\n }\n\n /** Force a specific quality tier (null = auto). */\n forceQuality(tier: QualityTier | null): void {\n this.forcedTier = tier;\n\n if (tier === null) {\n // Back to auto — reset counters\n this.consecutiveBad = 0;\n this.consecutiveGood = 0;\n return;\n }\n\n const targetIdx = this.profiles.findIndex((p) => p.tier === tier);\n if (targetIdx >= 0 && targetIdx !== this.currentIndex) {\n void this.switchTo(targetIdx);\n }\n }\n\n /** Check if auto-adaptation is active (not forced). */\n get isAuto(): boolean {\n return this.forcedTier === null;\n }\n\n // -----------------------------------------------------------------------\n // Private\n // -----------------------------------------------------------------------\n\n private evaluate(): void {\n if (this.forcedTier !== null || this.switching) return;\n\n const { packetLoss } = this.getAggregatedStats();\n\n if (packetLoss > this.degradeThreshold) {\n this.consecutiveGood = 0;\n this.consecutiveBad++;\n\n if (this.consecutiveBad >= this.degradeCount) {\n this.consecutiveBad = 0;\n this.degrade();\n }\n } else if (packetLoss < this.recoverThreshold) {\n this.consecutiveBad = 0;\n this.consecutiveGood++;\n\n if (this.consecutiveGood >= this.recoverCount) {\n this.consecutiveGood = 0;\n this.recover();\n }\n } else {\n // In between thresholds — reset both counters\n this.consecutiveBad = 0;\n this.consecutiveGood = 0;\n }\n }\n\n private degrade(): void {\n if (this.currentIndex >= this.profiles.length - 1) {\n this.logger?.debug(\"[adaptive] Already at lowest quality, cannot degrade further\");\n return;\n }\n void this.switchTo(this.currentIndex + 1);\n }\n\n private recover(): void {\n if (this.currentIndex <= 0) {\n this.logger?.debug(\"[adaptive] Already at highest quality, cannot recover further\");\n return;\n }\n void this.switchTo(this.currentIndex - 1);\n }\n\n private async switchTo(newIndex: number): Promise<void> {\n if (this.switching) return;\n if (newIndex === this.currentIndex) return;\n if (newIndex < 0 || newIndex >= this.profiles.length) return;\n\n this.switching = true;\n const from = this.profiles[this.currentIndex]!;\n const to = this.profiles[newIndex]!;\n\n this.logger?.info(\n `[adaptive] Quality change: ${from.tier} → ${to.tier} ` +\n `(${from.encoding.maxBitrateKbps}kbps → ${to.encoding.maxBitrateKbps}kbps)`,\n );\n\n try {\n await this.onQualityChange(from, to);\n this.currentIndex = newIndex;\n } catch (err) {\n this.logger?.error(\"[adaptive] Quality change failed:\", err);\n } finally {\n this.switching = false;\n }\n }\n}\n","/**\n * H.265/HEVC utilities.\n * HVCC to Annex-B conversion, VPS/SPS/PPS extraction, keyframe detection, RTP depacketization.\n */\n\nimport { NAL_START_CODE_4B, hasStartCodes, splitAnnexBToNals } from \"./nal-utils.js\";\n\n// --- HVCC to Annex-B conversion helpers ---\n\nfunction tryConvertWithLengthReader(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 4 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 4;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader16(\n data: Buffer,\n readLen: (buf: Buffer, offset: number) => number,\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n while (offset < data.length) {\n if (offset + 2 > data.length) return null;\n const nalLength = readLen(data, offset);\n offset += 2;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction tryConvertWithLengthReader24(\n data: Buffer,\n endian: \"be\" | \"le\",\n): Buffer | null {\n const result: Buffer[] = [];\n let offset = 0;\n let nalCount = 0;\n const readLen24 = (buf: Buffer, at: number): number => {\n if (at + 3 > buf.length) return 0;\n const b0 = buf[at]!;\n const b1 = buf[at + 1]!;\n const b2 = buf[at + 2]!;\n return endian === \"be\"\n ? ((b0 << 16) | (b1 << 8) | b2) >>> 0\n : ((b2 << 16) | (b1 << 8) | b0) >>> 0;\n };\n while (offset < data.length) {\n if (offset + 3 > data.length) return null;\n const nalLength = readLen24(data, offset);\n offset += 3;\n if (nalLength <= 0) return null;\n if (nalLength > data.length - offset) return null;\n result.push(NAL_START_CODE_4B);\n result.push(data.subarray(offset, offset + nalLength));\n offset += nalLength;\n nalCount++;\n }\n if (nalCount === 0) return null;\n return Buffer.concat(result);\n}\n\nfunction looksLikeSingleH265Nal(nalPayload: Buffer): boolean {\n if (nalPayload.length < 2) return false;\n const b0 = nalPayload[0];\n if (b0 === undefined) return false;\n if ((b0 & 0x80) !== 0) return false;\n const nalType = (b0 >> 1) & 0x3f;\n return nalType <= 40;\n}\n\n// --- Public API ---\n\n/**\n * Convert H.265 data from length-prefixed (HVCC) to Annex-B (start codes).\n * If already Annex-B, returns as-is.\n */\nexport function convertH265ToAnnexB(data: Buffer): Buffer {\n if (hasStartCodes(data)) return data;\n\n const sc4 = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n const sc3 = Buffer.from([0x00, 0x00, 0x01]);\n const maxScan = Math.min(64, data.length);\n const idx4 = data.subarray(0, maxScan).indexOf(sc4);\n if (idx4 > 0) return data.subarray(idx4);\n const idx3 = data.subarray(0, maxScan).indexOf(sc3);\n if (idx3 > 0) return data.subarray(idx3);\n\n // Try HVCC → AnnexB conversion\n const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));\n if (be) return be;\n const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));\n if (le) return le;\n\n const be24 = tryConvertWithLengthReader24(data, \"be\");\n if (be24) return be24;\n const le24 = tryConvertWithLengthReader24(data, \"le\");\n if (le24) return le24;\n\n const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));\n if (be16) return be16;\n const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));\n if (le16) return le16;\n\n if (looksLikeSingleH265Nal(data)) {\n return Buffer.concat([NAL_START_CODE_4B, data]);\n }\n return data;\n}\n\n/**\n * Get H.265 NAL unit type from a NAL payload (without start code).\n */\nexport function getH265NalType(nalPayload: Buffer): number | null {\n if (nalPayload.length < 1) return null;\n const b0 = nalPayload[0];\n if (b0 === undefined) return null;\n if ((b0 & 0x80) !== 0) return null;\n return (b0 >> 1) & 0x3f;\n}\n\n/**\n * Check if an H.265 NAL unit type is an IRAP (Intra Random Access Point) picture.\n * IRAP types: BLA (16-18), IDR (19-20), CRA (21).\n */\nexport function isH265Irap(nalType: number): boolean {\n return nalType >= 16 && nalType <= 23;\n}\n\n/**\n * Check if an H.265 Annex-B access unit is a keyframe (IRAP + VPS + SPS + PPS).\n */\nexport function isH265KeyframeAnnexB(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n let hasVps = false;\n let hasSps = false;\n let hasPps = false;\n let hasIrap = false;\n\n for (const nal of nals) {\n const nalType = getH265NalType(nal);\n if (nalType === null) continue;\n if (nalType === 32) hasVps = true;\n if (nalType === 33) hasSps = true;\n if (nalType === 34) hasPps = true;\n if (isH265Irap(nalType)) hasIrap = true;\n }\n\n return hasIrap && hasVps && hasSps && hasPps;\n}\n\n/**\n * Check if an H.265 Annex-B access unit contains an IRAP picture.\n */\nexport function isH265IrapAccessUnit(annexB: Buffer): boolean {\n const nals = splitAnnexBToNals(annexB);\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const b0 = nal[0];\n if (b0 === undefined) continue;\n if ((b0 & 0x80) !== 0) continue;\n const nalType = (b0 >> 1) & 0x3f;\n if (isH265Irap(nalType)) return true;\n }\n return false;\n}\n\n/**\n * Extract VPS, SPS, and PPS from an H.265 Annex-B access unit.\n */\nexport function extractH265ParamSets(annexB: Buffer): {\n vps?: Buffer;\n sps?: Buffer;\n pps?: Buffer;\n} {\n const nals = splitAnnexBToNals(annexB);\n let vps: Buffer | undefined;\n let sps: Buffer | undefined;\n let pps: Buffer | undefined;\n\n for (const nal of nals) {\n if (nal.length < 2) continue;\n const nalType = (nal[0]! >> 1) & 0x3f;\n if (nalType === 32) vps = nal;\n else if (nalType === 33) sps = nal;\n else if (nalType === 34) pps = nal;\n }\n\n const out: { vps?: Buffer; sps?: Buffer; pps?: Buffer } = {};\n if (vps) out.vps = vps;\n if (sps) out.sps = sps;\n if (pps) out.pps = pps;\n return out;\n}\n\n/**\n * H.265 RTP depacketizer (RFC 7798).\n * Handles single NAL units, AP aggregation, and FU fragmentation.\n * Returns complete NAL units in Annex-B format (with start codes).\n */\nexport class H265RtpDepacketizer {\n private fuParts: Buffer[] | null = null;\n\n private static parseRtpPayload(packet: Buffer): Buffer | null {\n if (!packet || packet.length < 12) return null;\n const version = (packet[0]! >> 6) & 0x03;\n if (version !== 2) return null;\n const padding = (packet[0]! & 0x20) !== 0;\n const extension = (packet[0]! & 0x10) !== 0;\n const csrcCount = packet[0]! & 0x0f;\n let offset = 12 + csrcCount * 4;\n if (offset > packet.length) return null;\n if (extension) {\n if (offset + 4 > packet.length) return null;\n const extLenWords = packet.readUInt16BE(offset + 2);\n offset += 4 + extLenWords * 4;\n if (offset > packet.length) return null;\n }\n let end = packet.length;\n if (padding) {\n const padLen = packet[packet.length - 1]!;\n if (padLen <= 0 || padLen > packet.length) return null;\n end = packet.length - padLen;\n if (end < offset) return null;\n }\n if (end <= offset) return null;\n return packet.subarray(offset, end);\n }\n\n reset(): void {\n this.fuParts = null;\n }\n\n push(payload: Buffer): Buffer[] {\n if (!payload || payload.length < 2) return [];\n\n const rtpPayload = H265RtpDepacketizer.parseRtpPayload(payload);\n if (rtpPayload) payload = rtpPayload;\n\n const h0 = payload[0]!;\n const h1 = payload[1]!;\n if ((h0 & 0x80) !== 0) return [];\n const nalType = (h0 >> 1) & 0x3f;\n\n // AP (48): aggregation packet\n if (nalType === 48) {\n let off = 2;\n const out: Buffer[] = [];\n while (off + 2 <= payload.length) {\n const size = payload.readUInt16BE(off);\n off += 2;\n if (size <= 0 || off + size > payload.length) return [];\n const nal = payload.subarray(off, off + size);\n off += size;\n if (nal.length) out.push(NAL_START_CODE_4B, nal);\n }\n return out.length ? [Buffer.concat(out)] : [];\n }\n\n // FU (49): fragmentation unit\n if (nalType === 49) {\n if (payload.length < 3) return [];\n const fuHeader = payload[2]!;\n const start = (fuHeader & 0x80) !== 0;\n const end = (fuHeader & 0x40) !== 0;\n const origType = fuHeader & 0x3f;\n const orig0 = (h0 & 0x81) | ((origType & 0x3f) << 1);\n const orig1 = h1;\n const frag = payload.subarray(3);\n\n if (start) {\n this.fuParts = [NAL_START_CODE_4B, Buffer.from([orig0, orig1]), frag];\n } else {\n if (!this.fuParts) return [];\n this.fuParts.push(frag);\n }\n\n if (end) {\n if (!this.fuParts) return [];\n const out = Buffer.concat(this.fuParts);\n this.fuParts = null;\n return [out];\n }\n\n return [];\n }\n\n // Single NAL unit\n return [Buffer.concat([NAL_START_CODE_4B, payload])];\n }\n}\n","/**\n * Adaptive WebRTC session — extends the base session pattern with:\n * - RTCP Receiver Report monitoring (packet loss, jitter, RTT)\n * - Source replacement (replaceTrack for seamless quality switching)\n * - Stats emission for the AdaptiveController\n *\n * Uses werift (optional peer dependency) for server-side WebRTC.\n */\n\nimport type {\n FrameSource,\n Logger,\n VideoCodec,\n AudioCodec,\n MediaFrame,\n} from \"./types.js\";\nimport { splitAnnexBToNals } from \"./nal-utils.js\";\nimport { convertH264ToAnnexB, isH264IdrAccessUnit } from \"./h264-utils.js\";\nimport { convertH265ToAnnexB, isH265IrapAccessUnit } from \"./h265-utils.js\";\n\n// ---------------------------------------------------------------------------\n// Werift type aliases (lazy-loaded optional peer dep)\n// ---------------------------------------------------------------------------\ntype WeriftPC = any;\ntype WeriftTrack = any;\n\nlet _werift: any | undefined;\n\nasync function loadWerift(): Promise<any> {\n if (_werift) return _werift;\n try {\n const moduleName = \"werift\";\n _werift = await (Function(\"m\", \"return import(m)\")(moduleName) as Promise<any>);\n return _werift;\n } catch {\n throw new Error(\n \"The 'werift' package is required for WebRTC support but is not installed. \" +\n \"Install it with: npm install werift\",\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface AdaptiveSessionOptions {\n sessionId: string;\n source: FrameSource;\n intercom?: {\n onAudioReceived: (data: Buffer, format: AudioCodec) => void | Promise<void>;\n };\n iceConfig?: {\n stunServers?: string[];\n turnServers?: Array<{ urls: string; username?: string; credential?: string }>;\n portRange?: [number, number];\n additionalHostAddresses?: string[];\n };\n /** Callback for RTCP stats (called every ~3s). */\n onStats?: (stats: SessionStats) => void;\n /** Enable verbose frame/RTP logging. */\n debug?: boolean;\n logger: Logger;\n}\n\nexport interface SessionStats {\n sessionId: string;\n /** Fraction of packets lost (0.0–1.0). */\n packetLoss: number;\n /** Interarrival jitter in ms. */\n jitterMs: number;\n /** Round-trip time in ms (from RTCP SR/RR). */\n rttMs: number;\n /** Total packets received. */\n packetsReceived: number;\n /** Total packets lost. */\n packetsLost: number;\n /** Timestamp. */\n timestamp: number;\n}\n\nexport interface SessionInfo {\n sessionId: string;\n state: \"new\" | \"connecting\" | \"connected\" | \"disconnected\" | \"closed\";\n createdAt: number;\n}\n\n// ---------------------------------------------------------------------------\n// AdaptiveSession\n// ---------------------------------------------------------------------------\n\nexport class AdaptiveSession {\n private readonly sessionId: string;\n private source: FrameSource;\n private readonly logger: Logger;\n private readonly intercom: AdaptiveSessionOptions[\"intercom\"];\n private readonly iceConfig: AdaptiveSessionOptions[\"iceConfig\"];\n private readonly onStats: AdaptiveSessionOptions[\"onStats\"];\n readonly debug: boolean;\n private readonly createdAt: number;\n\n private state: SessionInfo[\"state\"] = \"new\";\n private pc: WeriftPC | null = null;\n private videoTrack: WeriftTrack | null = null;\n private audioTrack: WeriftTrack | null = null;\n /** Transceiver senders for direct sendRtp (more reliable than track.writeRtp) */\n private videoSender: any = null;\n private audioSender: any = null;\n private feedAbort: AbortController | null = null;\n private closed = false;\n private statsTimer: ReturnType<typeof setInterval> | null = null;\n\n /** RTP sequence number counter (must increment per packet). */\n private videoSeqNum = 0;\n private audioSeqNum = 0;\n\n /** Previous RTCP stats for delta calculation. */\n private prevPacketsReceived = 0;\n private prevPacketsLost = 0;\n\n constructor(options: AdaptiveSessionOptions) {\n this.sessionId = options.sessionId;\n this.source = options.source;\n this.logger = options.logger;\n this.intercom = options.intercom;\n this.iceConfig = options.iceConfig;\n this.onStats = options.onStats;\n this.debug = options.debug ?? false;\n this.createdAt = Date.now();\n }\n\n /** Build PeerConnection options including H.264 codec config. */\n private async buildPcOptions(): Promise<{ werift: any; pcOptions: any }> {\n const werift = await loadWerift();\n\n const iceServers: any[] = [];\n if (this.iceConfig?.stunServers) {\n for (const url of this.iceConfig.stunServers) iceServers.push({ urls: url });\n }\n if (this.iceConfig?.turnServers) {\n for (const turn of this.iceConfig.turnServers) {\n iceServers.push({ urls: turn.urls, username: turn.username, credential: turn.credential });\n }\n }\n\n const pcOptions: any = {\n // H.264 + Opus codecs with RTCP feedback (matching Scrypted's proven config)\n codecs: {\n video: [\n new werift.RTCRtpCodecParameters({\n mimeType: \"video/H264\",\n clockRate: 90000,\n payloadType: 96,\n parameters: \"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\",\n rtcpFeedback: [\n { type: \"transport-cc\" },\n { type: \"ccm\", parameter: \"fir\" },\n { type: \"nack\" },\n { type: \"nack\", parameter: \"pli\" },\n { type: \"goog-remb\" },\n ],\n }),\n ],\n audio: [\n new werift.RTCRtpCodecParameters({\n mimeType: \"audio/PCMU\",\n clockRate: 8000,\n payloadType: 0,\n channels: 1,\n parameters: \"\",\n }),\n ],\n },\n };\n if (iceServers.length > 0) pcOptions.iceServers = iceServers;\n if (this.iceConfig?.portRange) pcOptions.icePortRange = this.iceConfig.portRange;\n if (this.iceConfig?.additionalHostAddresses) {\n pcOptions.iceAdditionalHostAddresses = this.iceConfig.additionalHostAddresses;\n }\n\n return { werift, pcOptions };\n }\n\n /** Create offer SDP (server → client). */\n async createOffer(): Promise<{ sdp: string; type: \"offer\" }> {\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n // ICE state monitoring\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.debug(`[session:${this.sessionId}] ICE: ${state}`);\n if (state === \"connected\") {\n this.state = \"connected\";\n this.startStatsCollection();\n } else if (state === \"disconnected\" || state === \"failed\" || state === \"closed\") {\n this.state = state === \"disconnected\" ? \"disconnected\" : \"closed\";\n void this.close();\n }\n });\n\n // Video track (sendonly) — save transceiver sender for sendRtp\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n const videoTransceiver = this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n this.videoSender = videoTransceiver.sender;\n\n // Audio track (sendrecv if intercom, sendonly otherwise)\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n const audioDir = this.intercom ? \"sendrecv\" : \"sendonly\";\n const audioTransceiver = this.pc.addTransceiver(this.audioTrack, { direction: audioDir });\n this.audioSender = audioTransceiver.sender;\n\n // Intercom: listen for incoming audio\n if (this.intercom) {\n const cb = this.intercom.onAudioReceived;\n audioTransceiver.onTrack.subscribe((track: any) => {\n track.onReceiveRtp.subscribe((pkt: any) => {\n try {\n const payload = pkt.payload as Buffer;\n if (payload?.length > 0) void cb(payload, \"Opus\");\n } catch (err) {\n this.logger.error(`[session:${this.sessionId}] Intercom error:`, err);\n }\n });\n });\n }\n\n const offer = await this.pc.createOffer();\n await this.pc.setLocalDescription(offer);\n\n // Wait for ICE gathering to complete so the offer includes candidates.\n // Without this, the offer has no candidates and the client can't connect.\n await new Promise<void>((resolve) => {\n if (this.pc.iceGatheringState === \"complete\") { resolve(); return; }\n this.pc.iceGatheringStateChange.subscribe((state: string) => {\n if (state === \"complete\") resolve();\n });\n // Timeout: don't wait forever if STUN is unreachable\n setTimeout(resolve, 5000);\n });\n\n // Use the local description which now includes gathered ICE candidates.\n // Force a=setup:actpass (let browser choose role) — werift defaults may cause DTLS issues.\n let finalSdp = this.pc.localDescription?.sdp ?? offer.sdp;\n // Ensure actpass so browser can be either active or passive\n finalSdp = finalSdp.replace(/a=setup:active\\r?\\n/g, \"a=setup:actpass\\r\\n\");\n this.state = \"connecting\";\n this.logger.info(`[session:${this.sessionId}] Offer created`);\n return { sdp: finalSdp, type: \"offer\" };\n }\n\n /** Handle WHEP answer: client sends SDP answer, we set remote description and start feeding. */\n async handleAnswer(answer: { sdp: string; type: \"answer\" }): Promise<void> {\n if (!this.pc) throw new Error(\"Call createOffer() first\");\n const werift = await loadWerift();\n const desc = new werift.RTCSessionDescription(answer.sdp, answer.type);\n await this.pc.setRemoteDescription(desc);\n this.logger.info(`[session:${this.sessionId}] Answer set, feeding started`);\n this.startFeedingFrames();\n }\n\n /**\n * Handle WHEP offer: client sends SDP offer, we create answer.\n *\n * Uses the server-creates-offer pattern internally: we create our own offer\n * with sendonly tracks, then use the client's offer codecs to build a\n * compatible answer. This avoids werift transceiver direction issues.\n */\n async handleOffer(clientOffer: { sdp: string; type: \"offer\" }): Promise<{ sdp: string; type: \"answer\" }> {\n const { werift, pcOptions } = await this.buildPcOptions();\n\n this.pc = new werift.RTCPeerConnection(pcOptions);\n\n this.pc.iceConnectionStateChange.subscribe((state: string) => {\n this.logger.debug(`[session:${this.sessionId}] ICE: ${state}`);\n if (state === \"connected\") {\n this.state = \"connected\";\n this.startStatsCollection();\n } else if (state === \"disconnected\" || state === \"failed\" || state === \"closed\") {\n this.state = state === \"disconnected\" ? \"disconnected\" : \"closed\";\n void this.close();\n }\n });\n\n // Set the client's offer as remote description.\n // werift creates transceivers from the offer's m-lines.\n const remoteDesc = new werift.RTCSessionDescription(clientOffer.sdp, clientOffer.type);\n await this.pc.setRemoteDescription(remoteDesc);\n\n // Find the transceivers werift created from the offer and attach our tracks.\n const transceivers = this.pc.getTransceivers();\n for (const t of transceivers) {\n const kind = t.receiver?.track?.kind ?? t.kind;\n if (kind === \"video\" && !this.videoTrack) {\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n await t.sender.replaceTrack(this.videoTrack);\n } else if (kind === \"audio\" && !this.audioTrack) {\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n await t.sender.replaceTrack(this.audioTrack);\n }\n }\n\n // Fallback: if no transceivers matched (shouldn't happen with valid offer)\n if (!this.videoTrack) {\n this.logger.warn(`[session:${this.sessionId}] No video transceiver found in offer, adding one`);\n this.videoTrack = new werift.MediaStreamTrack({ kind: \"video\" });\n this.pc.addTransceiver(this.videoTrack, { direction: \"sendonly\" });\n }\n if (!this.audioTrack) {\n this.logger.warn(`[session:${this.sessionId}] No audio transceiver found in offer, adding one`);\n this.audioTrack = new werift.MediaStreamTrack({ kind: \"audio\" });\n this.pc.addTransceiver(this.audioTrack, { direction: \"sendonly\" });\n }\n\n const answer = await this.pc.createAnswer();\n await this.pc.setLocalDescription(answer);\n this.state = \"connecting\";\n this.logger.info(`[session:${this.sessionId}] WHEP answer created`);\n\n this.startFeedingFrames();\n\n return { sdp: answer.sdp, type: \"answer\" };\n }\n\n /** Add ICE candidate. */\n async addIceCandidate(candidate: any): Promise<void> {\n if (!this.pc) throw new Error(\"Call createOffer() first\");\n const werift = await loadWerift();\n await this.pc.addIceCandidate(new werift.RTCIceCandidate(candidate));\n }\n\n /**\n * Detach the frame source (for connection pooling).\n * The session stays alive (ICE/DTLS connected) but stops feeding frames.\n * Call replaceSource() later to reattach a camera.\n */\n detachSource(): void {\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n this.logger.debug(`[session:${this.sessionId}] Source detached (idle)`);\n }\n\n /** Whether the session has an active feed (vs idle/pooled). */\n get isFeeding(): boolean {\n return this.feedAbort !== null && !this.feedAbort.signal.aborted;\n }\n\n /**\n * Replace the frame source (for seamless source switching).\n * The new source will take effect at the next keyframe.\n */\n replaceSource(newSource: FrameSource): void {\n this.source = newSource;\n // Abort old feed — the feeding loop will restart with new source\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n this.startFeedingFrames();\n }\n\n getInfo(): SessionInfo {\n return { sessionId: this.sessionId, state: this.state, createdAt: this.createdAt };\n }\n\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n this.state = \"closed\";\n this.logger.info(`[session:${this.sessionId}] Closing`);\n\n if (this.statsTimer) {\n clearInterval(this.statsTimer);\n this.statsTimer = null;\n }\n if (this.feedAbort) {\n this.feedAbort.abort();\n this.feedAbort = null;\n }\n try { await this.source.return(undefined); } catch { /* */ }\n if (this.pc) {\n try { await this.pc.close(); } catch { /* */ }\n this.pc = null;\n }\n this.videoTrack = null;\n this.audioTrack = null;\n }\n\n // -----------------------------------------------------------------------\n // Frame feeding\n // -----------------------------------------------------------------------\n\n private startFeedingFrames(): void {\n this.feedAbort = new AbortController();\n const { signal } = this.feedAbort;\n\n void (async () => {\n let gotKeyframe = false;\n let videoTimestampBase: number | null = null;\n let audioTimestampBase: number | null = null;\n let frameCount = 0;\n\n try {\n for await (const mediaFrame of this.source) {\n if (signal.aborted || this.closed) break;\n frameCount++;\n if (this.debug && (frameCount <= 5 || frameCount % 100 === 0)) {\n this.logger.debug(\n `[session:${this.sessionId}] Frame #${frameCount}: ${mediaFrame.type} ` +\n `size=${mediaFrame.frame.data.length} ` +\n (mediaFrame.type === \"video\" ? `key=${(mediaFrame.frame as any).isKeyframe}` : \"\"),\n );\n }\n\n if (mediaFrame.type === \"video\") {\n const frame = mediaFrame.frame;\n const annexB = frame.codec === \"H264\"\n ? convertH264ToAnnexB(frame.data)\n : convertH265ToAnnexB(frame.data);\n\n if (!gotKeyframe) {\n const isKey = frame.codec === \"H264\"\n ? isH264IdrAccessUnit(annexB)\n : isH265IrapAccessUnit(annexB);\n if (!isKey) continue;\n gotKeyframe = true;\n if (this.debug) {\n const iceState = this.pc?.iceConnectionState ?? \"unknown\";\n const connState = this.pc?.connectionState ?? \"unknown\";\n this.logger.info(\n `[session:${this.sessionId}] First keyframe at frame #${frameCount}, size=${annexB.length}, ` +\n `ICE=${iceState}, conn=${connState}`,\n );\n }\n }\n\n if (videoTimestampBase === null) videoTimestampBase = frame.timestampMicros;\n const rtpTs = Math.floor(\n ((frame.timestampMicros - videoTimestampBase) * 90000) / 1_000_000,\n ) >>> 0;\n\n // Filter out AUD (type 9) and SEI (type 6) — not needed in RTP\n const nals = splitAnnexBToNals(annexB).filter((n: Buffer) => {\n const t = n[0]! & 0x1f;\n return t !== 9 && t !== 6;\n });\n if (nals.length > 0 && this.videoTrack) {\n this.writeVideoNals(nals, rtpTs, frame.codec);\n if (this.debug && frameCount % 250 === 0) {\n this.logger.info(\n `[session:${this.sessionId}] ${frameCount} frames, ${this.rtpPacketsSent} RTP pkts, ` +\n `ICE=${this.pc?.iceConnectionState}, conn=${this.pc?.connectionState}`,\n );\n }\n }\n } else if (mediaFrame.type === \"audio\") {\n const frame = mediaFrame.frame;\n if (!this.audioSender) continue;\n\n // For G.711 (PCMU/PCMA): timestamp increments by frame size (160 samples per 20ms)\n // Using a simple counter is more reliable than wall-clock based timestamps\n if (audioTimestampBase === null) audioTimestampBase = 0;\n audioTimestampBase = ((audioTimestampBase as number) + frame.data.length) >>> 0;\n const rtpTs = audioTimestampBase as number;\n\n this.writeAudio(frame.data, rtpTs, frame.codec);\n }\n }\n } catch (err) {\n if (!signal.aborted && !this.closed) {\n this.logger.error(`[session:${this.sessionId}] Feed error:`, err);\n }\n } finally {\n if (!this.closed) {\n this.logger.info(`[session:${this.sessionId}] Feed ended`);\n void this.close();\n }\n }\n })();\n }\n\n /** Build a serialized RTP packet for sender.sendRtp(). */\n private buildRtpBuffer(\n weriftModule: any,\n payload: Buffer,\n rtpTs: number,\n payloadType: number,\n marker: boolean,\n isVideo: boolean,\n ): Buffer {\n const header = new weriftModule.RtpHeader();\n header.payloadType = payloadType;\n header.timestamp = rtpTs;\n header.marker = marker;\n // CRITICAL: sequence number MUST increment per packet.\n // werift adds seqOffset but does NOT auto-increment — if all packets\n // have seq=0, SRTP replay protection drops them as duplicates.\n header.sequenceNumber = isVideo\n ? (this.videoSeqNum = (this.videoSeqNum + 1) & 0xffff)\n : (this.audioSeqNum = (this.audioSeqNum + 1) & 0xffff);\n const pkt = new weriftModule.RtpPacket(header, payload);\n return pkt.serialize();\n }\n\n /** Max RTP payload size (MTU 1200 to stay under typical network MTU). */\n private static readonly MAX_RTP_PAYLOAD = 1200;\n\n private rtpPacketsSent = 0;\n\n private writeVideoNals(nals: Buffer[], rtpTs: number, codec: VideoCodec): void {\n if (!this.videoSender || !_werift) return;\n const pt = codec === \"H264\" ? 96 : 97;\n\n const sendPkt = (payload: Buffer, marker: boolean) => {\n try {\n const buf = this.buildRtpBuffer(_werift, payload, rtpTs, pt, marker, true);\n this.videoSender.sendRtp(buf);\n this.rtpPacketsSent++;\n } catch (err) {\n if (this.rtpPacketsSent <= 10) {\n this.logger.error(`[session:${this.sessionId}] sendRtp error #${this.rtpPacketsSent}:`, err);\n }\n }\n };\n\n for (let i = 0; i < nals.length; i++) {\n const nal = nals[i]!;\n const isLastNal = i === nals.length - 1;\n\n if (nal.length <= AdaptiveSession.MAX_RTP_PAYLOAD) {\n sendPkt(nal, isLastNal);\n } else {\n // FU-A fragmentation (RFC 6184 §5.8)\n const nalHeader = nal[0]!;\n const fnri = nalHeader & 0xe0;\n const nalType = nalHeader & 0x1f;\n const fuIndicator = fnri | 28;\n const nalBody = nal.subarray(1);\n\n let offset = 0;\n let isFirst = true;\n while (offset < nalBody.length) {\n const end = Math.min(offset + AdaptiveSession.MAX_RTP_PAYLOAD - 2, nalBody.length);\n const isLast = end >= nalBody.length;\n\n let fuHeader = nalType;\n if (isFirst) fuHeader |= 0x80;\n if (isLast) fuHeader |= 0x40;\n\n const fragment = Buffer.alloc(2 + (end - offset));\n fragment[0] = fuIndicator;\n fragment[1] = fuHeader;\n nalBody.copy(fragment, 2, offset, end);\n\n sendPkt(fragment, isLastNal && isLast);\n offset = end;\n isFirst = false;\n }\n }\n }\n }\n\n private writeAudio(data: Buffer, rtpTs: number, codec?: string): void {\n if (!this.audioSender || !_werift) return;\n // Use correct payload type: PCMU=0, Opus=111\n const pt = codec === \"Pcmu\" || codec === \"Pcma\" ? 0 : 111;\n try {\n const buf = this.buildRtpBuffer(_werift, data, rtpTs, pt, true, false);\n this.audioSender.sendRtp(buf);\n } catch (err) {\n this.logger.debug(`[session:${this.sessionId}] Audio write error:`, err);\n }\n }\n\n // -----------------------------------------------------------------------\n // RTCP stats collection\n // -----------------------------------------------------------------------\n\n private startStatsCollection(): void {\n if (this.statsTimer || !this.onStats) return;\n\n this.statsTimer = setInterval(() => {\n if (!this.pc || this.closed) return;\n this.collectStats();\n }, 3_000);\n }\n\n private collectStats(): void {\n if (!this.pc || !this.onStats) return;\n\n try {\n // werift exposes getReceivers() and getSenders() on RTCPeerConnection.\n // For video sender stats, we check RTCP RR feedback from the receiver.\n const senders = this.pc.getSenders?.() ?? [];\n\n for (const sender of senders) {\n const track = sender.track;\n if (!track || track.kind !== \"video\") continue;\n\n // werift tracks RTCP receiver reports via sender.rtcpReceiveReport\n // or via the transport's RTCP event. The exact API depends on werift version.\n // Fallback: use werift's built-in stats if available.\n const report = sender.lastReceiverReport ?? sender.rtcpReport;\n if (!report) continue;\n\n const fractionLost = report.fractionLost ?? 0;\n const packetsLost = report.packetsLost ?? report.cumulativeLost ?? 0;\n const jitter = report.jitter ?? 0;\n const rtt = report.roundTripTime ?? report.rtt ?? 0;\n\n const packetLoss = fractionLost / 256; // Fraction lost is 0–255\n\n this.onStats({\n sessionId: this.sessionId,\n packetLoss,\n jitterMs: jitter,\n rttMs: rtt * 1000, // seconds → ms\n packetsReceived: 0, // Not available from sender side\n packetsLost,\n timestamp: Date.now(),\n });\n return; // One video sender is enough\n }\n\n // Fallback: no RTCP report available yet — emit zeros\n // (Client-reported stats will supplement this)\n } catch {\n // RTCP not yet available — normal during early connection\n }\n }\n}\n"],"mappings":"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","names":["i","tryConvertWithLengthReader","tryConvertWithLengthReader16","tryConvertWithLengthReader24","crypto"]}
@@ -0,0 +1,3 @@
1
+ import '@camstack/types';
2
+ export { W as WebrtcAdaptiveAddon, W as default } from './addon--i9xjbhN.cjs';
3
+ import 'node:events';
@@ -0,0 +1,3 @@
1
+ import '@camstack/types';
2
+ export { W as WebrtcAdaptiveAddon, W as default } from './addon--i9xjbhN.js';
3
+ import 'node:events';