@camstack/sdk 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/index.cjs +1169 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +1087 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/backend-client.ts","../src/detection.ts","../src/devices.ts","../src/features.ts","../src/adaptive-stream.ts"],"sourcesContent":["// ─── Client ──────────────────────────────────────────────────────────\nexport { BackendClient, createBackendClient } from \"./backend-client.js\";\n\n// ─── tRPC AppRouter type (for advanced typed access) ─────────────────\nexport type { BackendAppRouter } from \"./backend-router.js\";\nexport type { BackendAppRouter as AppRouter } from \"./backend-router.js\";\n\n// ─── Client config types ────────────────────────────────────────────\nexport type { BackendClientConfig } from \"./types.js\";\n\n// ─── Detection classes ──────────────────────────────────────────────\nexport {\n DetectionClass,\n animalClasses,\n personClasses,\n vehicleClasses,\n faceClasses,\n licensePlateClasses,\n motionClasses,\n packageClasses,\n audioClasses,\n audioLabelClasses,\n doorbellClasses,\n sensorLabelClasses,\n detectionClassesDefaultMap,\n isFaceClassname,\n isPlateClassname,\n isAnimalClassname,\n isPersonClassname,\n isVehicleClassname,\n isMotionClassname,\n isDoorbellClassname,\n isPackageClassname,\n isAudioClassname,\n isSensorLabelClassname,\n isLabelDetection,\n getParentClass,\n getParentDetectionClass,\n defaultDetectionClasses,\n DEFAULT_ENABLED_CLASSES,\n TIMELINE_PRESET_CRITICAL,\n TIMELINE_PRESET_IMPORTANT,\n TIMELINE_PRESET_ALL,\n getClassesForTimelinePreset,\n} from \"./detection.js\";\nexport type { TimelineEventPreset } from \"./detection.js\";\n\n// ─── Device types ───────────────────────────────────────────────────\nexport {\n RAW_TO_CANONICAL,\n HA_DOMAIN_TYPE_MAP,\n SCRYPTED_TYPE_TO_CANONICAL,\n getCanonicalDeviceType,\n ELIGIBLE_SCRYPTED_DEVICE_TYPES,\n ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET,\n ELIGIBLE_HA_DOMAINS,\n ELIGIBLE_HA_DOMAINS_SET,\n} from \"./devices.js\";\nexport type {\n CanonicalDeviceType,\n DeviceCommandAction,\n AssociatedDeviceState,\n Device,\n DeviceAction,\n DeviceActionParam,\n DeviceCommand,\n CommandResult,\n AssociatedDeviceSource,\n EligibleDevice,\n} from \"./devices.js\";\n\n// ─── Timeline & events ─────────────────────────────────────────────\nexport type {\n TimelineEventSeverity,\n MotionFragmentType,\n DetectionEvent,\n MotionItem,\n TimelineArtifact,\n AudioLevelSegment,\n TimelineRecordingSegment,\n CameraDayDataResponse,\n ClusteredDayDataResponse,\n TimelineCluster,\n DetectionGroup,\n ClusteredDayDataQuery,\n GroupedEventsQuery,\n GroupedEventsResult,\n ClusterEventsQuery,\n ClusterEventsResult,\n ReelEvent,\n ReelEventsQuery,\n ReelEventsResult,\n // Backward-compat aliases\n TimelineDetectionEvent,\n TimelineMotionItem,\n TimelineArtifactItem,\n TimelineClusterFromServer,\n} from \"./timeline.js\";\n\n// ─── Camera & PTZ ───────────────────────────────────────────────────\nexport type {\n CameraSourceType,\n StreamMethod,\n PanTiltZoomCommand,\n PanTiltZoomCapabilities,\n CameraAccessorySwitchKind,\n CameraStatusEntry,\n CamerasStatusResult,\n} from \"./camera.js\";\n\n// ─── Feature matrix ─────────────────────────────────────────────────\nexport {\n FEATURE_MATRIX,\n isFeatureAvailable,\n getSourceFeatures,\n getBackendRequiredFeatures,\n} from \"./features.js\";\nexport type {\n FeatureId,\n PlatformId,\n SourceType,\n FeatureEntry,\n} from \"./features.js\";\n\n// ─── Adaptive streaming ─────────────────────────────────────────────\nexport {\n selectOptimalStream,\n getNextEvalInterval,\n} from \"./adaptive-stream.js\";\nexport type {\n StreamConstraints,\n StreamAssignment,\n StreamSelectionRequest,\n StreamSelectionResponse,\n} from \"./adaptive-stream.js\";\n\n// ─── NVR types ──────────────────────────────────────────────────────\nexport type {\n StreamOption,\n NvrCamera,\n CameraAccessory,\n StreamInfo,\n NormalizedBoundingBox,\n DetectionData,\n EventSnapshotData,\n NvrEvent,\n RecordingSegment,\n MotionBucket,\n LiveDetection,\n LiveMotionData,\n LiveDetectionData,\n LiveAudioData,\n LiveMotionEvent,\n LiveDetectionEvent,\n LiveAudioEvent,\n LiveGenericEvent,\n LiveReviewEvent,\n LiveEvent,\n LiveEventType,\n VideoClipsQuery,\n NvrVideoClip,\n VideoClipsResult,\n VideoClipUrlResult,\n CameraDayData,\n CameraStatus,\n EventsQuery,\n MotionQuery,\n RecordingsQuery,\n NvrConfig,\n ProviderConfigs,\n} from \"./nvr.js\";\n","/**\n * Backend client — connects directly to the CamStack NestJS backend via tRPC.\n *\n * Use this when the client needs to talk to the backend server directly\n * (admin UI, local agent, embedded mode) rather than going through the proxy.\n *\n * Features: full typed access to all backend tRPC routes (auth, devices, providers,\n * pipeline, agents, addons, bridge pipeline, recording, events, live subscriptions).\n */\n\nimport { createTRPCClient, createWSClient, wsLink, httpLink, splitLink } from \"@trpc/client\";\nimport superjson from \"superjson\";\nimport type { BackendAppRouter } from \"./backend-router.js\";\n\n// ---------------------------------------------------------------------------\n// Settings section union — must match the backend settings.router sectionSchema\n// ---------------------------------------------------------------------------\n\nexport type SettingsSection =\n | 'server' | 'auth' | 'features' | 'storage' | 'logging' | 'addons'\n | 'recording' | 'streaming' | 'ffmpeg' | 'detection' | 'backup' | 'retention'\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface BackendClientConfig {\n /** Backend server URL (e.g. \"http://localhost:4443\") */\n readonly serverUrl: string;\n /** JWT token for authentication */\n readonly token?: string;\n /** Connection timeout in ms (default: 10000) */\n readonly connectTimeoutMs?: number;\n /** Use WebSocket for all queries/mutations (default: true in browser, false in Node) */\n readonly useWebSocket?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Client\n// ---------------------------------------------------------------------------\n\nexport class BackendClient {\n /** Raw tRPC client — for advanced usage / direct path access */\n readonly trpc: ReturnType<typeof createTRPCClient<BackendAppRouter>>;\n readonly serverUrl: string;\n private token: string | undefined;\n private wsClient: ReturnType<typeof createWSClient> | null = null;\n\n constructor(config: BackendClientConfig) {\n this.serverUrl = config.serverUrl.replace(/\\/+$/, \"\");\n this.token = config.token;\n\n const isBrowser = typeof window !== 'undefined';\n const useWs = config.useWebSocket ?? isBrowser;\n\n const headers = (): Record<string, string> => {\n const h: Record<string, string> = {};\n if (this.token) {\n h[\"Authorization\"] = `Bearer ${this.token}`;\n }\n return h;\n };\n\n if (useWs) {\n // WebSocket: single persistent connection for all queries/mutations/subscriptions\n const wsUrl = this.serverUrl.replace(/^http/, 'ws') + '/trpc';\n this.wsClient = createWSClient({\n url: wsUrl,\n connectionParams: () => ({\n token: this.token,\n }),\n retryDelayMs: () => 2000,\n });\n\n this.trpc = createTRPCClient<BackendAppRouter>({\n links: [\n wsLink({\n client: this.wsClient,\n transformer: superjson,\n }),\n ],\n });\n } else {\n // HTTP: for Node.js environments (agents, CLI, scripts)\n this.trpc = createTRPCClient<BackendAppRouter>({\n links: [\n httpLink({\n url: `${this.serverUrl}/trpc`,\n headers,\n transformer: superjson,\n }),\n ],\n });\n }\n }\n\n /** Update the auth token (e.g. after login) */\n setToken(token: string): void {\n this.token = token;\n }\n\n /** Close the WebSocket connection (if using WS transport) */\n close(): void {\n this.wsClient?.close();\n }\n\n // ─── Auth ──────────────────────────────────────────────────────────\n\n async login(username: string, password: string) {\n return this.trpc.auth.login.mutate({ username, password });\n }\n\n async getMe() {\n return this.trpc.auth.me.query();\n }\n\n async logout() {\n return this.trpc.auth.logout.mutate();\n }\n\n // ─── System ────────────────────────────────────────────────────────\n\n async getSystemInfo() {\n return this.trpc.system.info.query();\n }\n\n async getFeatureFlags() {\n return this.trpc.system.featureFlags.query();\n }\n\n // ─── Providers ─────────────────────────────────────────────────────\n\n async listProviders() {\n return this.trpc.providers.list.query();\n }\n\n async getProvider(providerId: string) {\n return this.trpc.providers.get.query({ id: providerId });\n }\n\n async startProvider(providerId: string) {\n return this.trpc.providers.start.mutate({ id: providerId });\n }\n\n async stopProvider(providerId: string) {\n return this.trpc.providers.stop.mutate({ id: providerId });\n }\n\n async listProviderTypes() {\n return this.trpc.providerConfig.getAvailableTypes.query();\n }\n\n // ─── Devices ───────────────────────────────────────────────────────\n\n async listDevices() {\n return this.trpc.devices.list.query();\n }\n\n async getDevice(deviceId: string) {\n return this.trpc.devices.get.query({ id: deviceId });\n }\n\n async discoverDevices(providerId: string) {\n return this.trpc.devices.discoverDevices.query({ providerId });\n }\n\n async adoptDevice(providerId: string, externalId: string) {\n return this.trpc.devices.adoptDevice.mutate({ providerId, externalId });\n }\n\n async createDevice(providerId: string, config: Record<string, unknown>) {\n return this.trpc.devices.createDevice.mutate({ providerId, config });\n }\n\n /** Returns the URL path for a static asset served by an addon. */\n getAddonAssetUrl(addonId: string, assetPath: string): string {\n return `/api/addon-assets/${addonId}/${assetPath}`;\n }\n\n // ─── Addons ────────────────────────────────────────────────────────\n\n async listAddons() {\n return this.trpc.addons.list.query();\n }\n\n async getAddonConfigSchema(addonId: string) {\n return this.trpc.addons.getConfigSchema.query({ addonId });\n }\n\n async getAddonConfig(addonId: string) {\n return this.trpc.addons.getConfig.query({ addonId });\n }\n\n async updateAddonConfig(addonId: string, config: Record<string, unknown>) {\n return this.trpc.addons.updateConfig.mutate({ addonId, config });\n }\n\n async getAddonLogs(addonId: string, options?: { limit?: number; level?: 'debug' | 'info' | 'warn' | 'error' }) {\n return this.trpc.addons.getLogs.query({ addonId, ...options });\n }\n\n // ─── Known Faces ───────────────────────────────────────────────────\n\n async listKnownFaces() {\n return this.trpc.knownFaces.list.query();\n }\n\n async registerFace(label: string, cropBase64: string, group?: string) {\n return this.trpc.knownFaces.register.mutate({ label, cropBase64, group });\n }\n\n // ─── Pipeline ──────────────────────────────────────────────────────\n\n async listPipelines() {\n return this.trpc.bridgePipeline.listAddons.query();\n }\n\n async getPipelineStatus(deviceId: string) {\n return this.trpc.bridgePipeline.getPipeline.query({ deviceId });\n }\n\n // ─── Agents ────────────────────────────────────────────────────────\n\n async listAgents() {\n return this.trpc.agents.listAgents.query();\n }\n\n async dispatchTask(agentId: string, task: Record<string, unknown>) {\n return this.trpc.agents.dispatchTask.mutate({ capability: agentId, input: task });\n }\n\n async getProcessTree() {\n return this.trpc.agent.processTree.query();\n }\n\n // ─── Bridge Pipeline ───────────────────────────────────────────────\n\n async bridgeListAddons() {\n return this.trpc.bridgePipeline.listAddons.query();\n }\n\n async bridgeGetPipeline(deviceId: string) {\n return this.trpc.bridgePipeline.getPipeline.query({ deviceId });\n }\n\n async bridgeSetPipeline(deviceId: string, config: { video: unknown[]; audio?: unknown }) {\n return this.trpc.bridgePipeline.setPipeline.mutate({ deviceId, config: config as any });\n }\n\n async bridgeValidatePipeline(config: { video: unknown[]; audio?: unknown }) {\n return this.trpc.bridgePipeline.validatePipeline.query(config as any);\n }\n\n async bridgeGetAddonConfig(addonId: string) {\n return this.trpc.bridgePipeline.getAddonConfig.query({ addonId });\n }\n\n async bridgeSetAddonConfig(addonId: string, config: Record<string, unknown>) {\n return this.trpc.bridgePipeline.setAddonConfig.mutate({ addonId, config });\n }\n\n // ─── Bridge Addons ─────────────────────────────────────────────────\n\n async bridgeListPackages() {\n return this.trpc.bridgeAddons.listPackages.query();\n }\n\n async bridgeListAddonsPackages() {\n return this.trpc.bridgeAddons.listAddons.query();\n }\n\n async bridgeInstallPackage(packageName: string, version?: string) {\n return this.trpc.bridgeAddons.installPackage.mutate({ packageName, version });\n }\n\n async bridgeUninstallPackage(packageName: string) {\n return this.trpc.bridgeAddons.uninstallPackage.mutate({ packageName });\n }\n\n // ─── Recording ─────────────────────────────────────────────────────\n\n async getRecordingConfig(deviceId: string) {\n return this.trpc.recording.getConfig.query({ deviceId });\n }\n\n async getRecordingPolicy(deviceId: string) {\n return this.trpc.recording.getPolicy.query({ deviceId });\n }\n\n async getRecordingPolicyStatus(deviceId: string) {\n return this.trpc.recording.getPolicyStatus.query({ deviceId });\n }\n\n async getRecordingSegments(deviceId: string, streamId: string, startTime: number, endTime: number) {\n return this.trpc.recording.getSegments.query({ deviceId, streamId, startTime, endTime });\n }\n\n // ─── Events ────────────────────────────────────────────────────────\n\n async getEvents(deviceId: string, options?: { limit?: number; offset?: number }) {\n return this.trpc.events.query.query({ deviceId, ...options });\n }\n\n // ─── Logs ──────────────────────────────────────────────────────────\n\n async getLogs(options?: { level?: 'debug' | 'info' | 'warn' | 'error'; limit?: number; since?: number; until?: number; scope?: string[] }) {\n return this.trpc.logs.query.query(options ?? {});\n }\n\n // ─── REPL ──────────────────────────────────────────────────────────\n\n async replEval(code: string, scope?: { type: 'system' } | { type: 'device'; deviceId: string } | { type: 'provider'; providerId: string } | { type: 'addon'; addonId: string }) {\n return this.trpc.repl.execute.mutate({ code, scope: scope ?? { type: 'system' as const } });\n }\n\n // ─── Users ─────────────────────────────────────────────────────────\n\n async listUsers() {\n return this.trpc.users.list.query();\n }\n\n async createUser(username: string, password: string, role: 'super_admin' | 'admin' | 'viewer') {\n return this.trpc.users.create.mutate({ username, password, role });\n }\n\n // ─── Session Tracker ───────────────────────────────────────────────\n\n async getTrackingSessions(deviceId?: string) {\n return this.trpc.session.getActiveTracks.query({ deviceId });\n }\n\n // ─── Processes ─────────────────────────────────────────────────────\n\n async listProcesses() {\n return this.trpc.processes.listProcesses.query();\n }\n\n async enableProvider(providerId: string) {\n return this.trpc.processes.enableProvider.mutate({ id: providerId });\n }\n\n async disableProvider(providerId: string) {\n return this.trpc.processes.disableProvider.mutate({ id: providerId });\n }\n\n // ─── Settings ──────────────────────────────────────────────────────\n\n /** Fetch the UI schema for a single section, or all sections if omitted. */\n async getSettingsSchema(section?: SettingsSection) {\n return this.trpc.settings.getSchema.query({ section });\n }\n\n async getSettings(section: SettingsSection) {\n return this.trpc.settings.get.query({ section });\n }\n\n async updateSettings(section: SettingsSection, data: Record<string, unknown>) {\n return this.trpc.settings.update.mutate({ section, data });\n }\n}\n\n/**\n * Create a BackendClient instance with the given config.\n * Convenience factory function.\n */\nexport function createBackendClient(config: BackendClientConfig): BackendClient {\n return new BackendClient(config);\n}\n","/**\n * Detection classes — shared between CamStack app and proxy.\n * Aligned with scrypted-advanced-notifier/src/detectionClasses.ts.\n */\n\n// ---------------------------------------------------------------------------\n// Enum — matches the plugin enum exactly\n// ---------------------------------------------------------------------------\n\nexport enum DetectionClass {\n Motion = \"motion\",\n Person = \"person\",\n Vehicle = \"vehicle\",\n Animal = \"animal\",\n Audio = \"audio\",\n Face = \"face\",\n Plate = \"plate\",\n Package = \"package\",\n Doorbell = \"doorbell\",\n Sensor = \"sensor\",\n}\n\n// ---------------------------------------------------------------------------\n// Sub-class arrays (for classname → parent mapping)\n// ---------------------------------------------------------------------------\n\nexport const animalClasses = [\n DetectionClass.Animal,\n \"dog_cat\", \"dog\", \"cat\", \"horse\", \"sheep\", \"cow\", \"elephant\", \"bear\",\n \"zebra\", \"giraffe\", \"mouse\", \"rabbit\", \"deer\", \"lion\", \"tiger\",\n \"bird\", \"eagle\", \"owl\", \"pigeon\",\n \"fish\", \"whale\", \"dolphin\",\n \"snake\", \"turtle\", \"lizard\",\n];\n\nexport const personClasses = [\n DetectionClass.Person,\n \"people\", \"pedestrian\", \"rider\", \"driver\", \"cyclist\", \"skier\", \"skateboarder\",\n \"face\", \"hand\", \"head\", \"body\",\n];\n\nexport const vehicleClasses = [\n DetectionClass.Vehicle,\n \"car\", \"truck\", \"bus\", \"motorcycle\", \"bicycle\", \"van\",\n \"ambulance\", \"police_car\", \"fire_truck\",\n \"train\", \"subway\", \"tram\",\n \"airplane\", \"boat\", \"ship\", \"helicopter\",\n];\n\nexport const faceClasses = [\n DetectionClass.Face,\n \"eyes\", \"nose\", \"mouth\", \"ears\", \"eyebrows\",\n \"left_eye\", \"right_eye\", \"pupil\", \"iris\", \"eyelid\", \"eye_corner\",\n \"upper_lip\", \"lower_lip\", \"teeth\",\n \"chin\", \"cheek\", \"forehead\", \"jaw\",\n \"glasses\", \"sunglasses\", \"facial_hair\", \"beard\", \"mustache\",\n \"facial_landmark\", \"facial_keypoint\",\n];\n\nexport const licensePlateClasses = [\n DetectionClass.Plate,\n \"license_plate\", \"front_plate\", \"rear_plate\", \"motorcycle_plate\",\n \"temporary_plate\", \"dealer_plate\", \"licensePlate\",\n \"plate_number\", \"plate_character\", \"plate_digit\", \"plate_letter\",\n \"plate_symbol\", \"plate_region\", \"plate_country_identifier\",\n \"plate_frame\", \"plate_bolt\", \"plate_sticker\", \"plate_validation_tag\",\n \"damaged_plate\", \"obscured_plate\", \"dirty_plate\",\n];\n\nexport const motionClasses = [DetectionClass.Motion, \"movement\", \"other\"];\n\nexport const packageClasses = [DetectionClass.Package, \"packet\"];\n\nexport const audioClasses: string[] = [DetectionClass.Audio];\n\n/** Common YAMNet audio labels (advanced-notifier). Parent: Audio. */\nexport const audioLabelClasses = [\n \"speech\", \"scream\", \"babbling\", \"yell\", \"bellow\", \"whoop\", \"whispering\", \"laughter\", \"snicker\",\n \"crying\", \"cry\", \"sigh\", \"singing\", \"choir\", \"chant\", \"mantra\", \"child_singing\", \"rapping\", \"humming\",\n \"groan\", \"grunt\", \"whistling\", \"breathing\", \"wheeze\", \"snoring\", \"gasp\", \"pant\", \"snort\", \"cough\",\n \"throat_clearing\", \"sneeze\", \"sniff\", \"cheering\", \"applause\", \"chatter\", \"crowd\", \"children_playing\",\n \"bark\", \"yip\", \"howl\", \"bow-wow\", \"growling\", \"whimper_dog\", \"purr\", \"meow\", \"hiss\", \"caterwaul\",\n \"pets\", \"livestock\", \"doorbell\", \"ding-dong\", \"door\", \"slam\", \"knock\", \"alarm\", \"telephone\",\n \"music\", \"dog\", \"dogs\",\n];\n\nexport const doorbellClasses = [DetectionClass.Doorbell, \"ring\"];\n\n/** Sensor types (advanced-notifier SupportedSensorType). Parent: Sensor. */\nexport const sensorLabelClasses = [\n \"lock\", \"binary\", \"flood\", \"entry\",\n \"door\", \"leak\", \"door_open\", \"flooded\", \"entry_open\",\n];\n\n// ---------------------------------------------------------------------------\n// Classname → parent DetectionClass map\n// ---------------------------------------------------------------------------\n\nexport const detectionClassesDefaultMap: Record<string, DetectionClass> = {\n ...animalClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Animal }), {} as Record<string, DetectionClass>),\n ...personClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Person }), {} as Record<string, DetectionClass>),\n ...vehicleClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Vehicle }), {} as Record<string, DetectionClass>),\n ...motionClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Motion }), {} as Record<string, DetectionClass>),\n ...packageClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Package }), {} as Record<string, DetectionClass>),\n ...faceClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Face }), {} as Record<string, DetectionClass>),\n ...licensePlateClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Plate }), {} as Record<string, DetectionClass>),\n ...audioClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Audio }), {} as Record<string, DetectionClass>),\n ...audioLabelClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Audio }), {} as Record<string, DetectionClass>),\n ...doorbellClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Doorbell }), {} as Record<string, DetectionClass>),\n ...sensorLabelClasses.reduce((tot, curr) => ({ ...tot, [curr]: DetectionClass.Sensor }), {} as Record<string, DetectionClass>),\n};\n\n// ---------------------------------------------------------------------------\n// Classifier helpers\n// ---------------------------------------------------------------------------\n\nexport const isFaceClassname = (c: string) => faceClasses.includes(c);\nexport const isPlateClassname = (c: string) => licensePlateClasses.includes(c);\nexport const isAnimalClassname = (c: string) => animalClasses.includes(c);\nexport const isPersonClassname = (c: string) => personClasses.includes(c);\nexport const isVehicleClassname = (c: string) => vehicleClasses.includes(c);\nexport const isMotionClassname = (c: string) => motionClasses.includes(c);\nexport const isDoorbellClassname = (c: string) => doorbellClasses.includes(c);\nexport const isPackageClassname = (c: string) => packageClasses.includes(c);\nexport const isAudioClassname = (c: string) =>\n audioClasses.includes(c) || audioLabelClasses.includes(c);\nexport const isSensorLabelClassname = (c: string) => sensorLabelClasses.includes(c);\nexport const isLabelDetection = (c: string) => isFaceClassname(c) || isPlateClassname(c);\n\nexport const getParentClass = (className: string): DetectionClass | undefined =>\n detectionClassesDefaultMap[className];\n\nexport const getParentDetectionClass = (det: { label?: string; className: string }) => {\n const { className } = det;\n const baseMap: Record<string, DetectionClass> = {\n [DetectionClass.Face]: DetectionClass.Person,\n [DetectionClass.Plate]: DetectionClass.Vehicle,\n };\n const parentGroup = detectionClassesDefaultMap[className];\n if (parentGroup && parentGroup !== className) return parentGroup;\n return baseMap[className];\n};\n\n// ---------------------------------------------------------------------------\n// All detection classes (enum values) used in filter UI\n// ---------------------------------------------------------------------------\n\nexport const defaultDetectionClasses = Object.values(DetectionClass);\n\n/** Default enabled classes: all except Motion. */\nexport const DEFAULT_ENABLED_CLASSES = defaultDetectionClasses.filter(\n (c) => c !== DetectionClass.Motion,\n);\n\n// ---------------------------------------------------------------------------\n// Timeline event presets (by severity)\n// ---------------------------------------------------------------------------\n\nexport type TimelineEventPreset = \"all\" | \"important\" | \"critical\" | \"custom\";\n\n/** Classes for \"critical\" preset: security-relevant only (person, doorbell, package). */\nexport const TIMELINE_PRESET_CRITICAL: string[] = [\n DetectionClass.Person,\n DetectionClass.Doorbell,\n DetectionClass.Package,\n];\n\n/** Classes for \"important\" preset: critical + vehicle, animal, audio, face, plate. */\nexport const TIMELINE_PRESET_IMPORTANT: string[] = [\n ...TIMELINE_PRESET_CRITICAL,\n DetectionClass.Vehicle,\n DetectionClass.Animal,\n DetectionClass.Audio,\n DetectionClass.Face,\n DetectionClass.Plate,\n];\n\n/** Classes for \"all\" preset: same as DEFAULT_ENABLED_CLASSES. */\nexport const TIMELINE_PRESET_ALL: string[] = [...DEFAULT_ENABLED_CLASSES];\n\n/** Get enabled classes for a preset. For \"custom\", pass customClasses. */\nexport function getClassesForTimelinePreset(\n preset: TimelineEventPreset,\n customClasses?: string[]\n): string[] {\n switch (preset) {\n case \"critical\":\n return TIMELINE_PRESET_CRITICAL;\n case \"important\":\n return TIMELINE_PRESET_IMPORTANT;\n case \"all\":\n return TIMELINE_PRESET_ALL;\n case \"custom\":\n return customClasses?.length ? customClasses : DEFAULT_ENABLED_CLASSES;\n default:\n return DEFAULT_ENABLED_CLASSES;\n }\n}\n","/**\n * Device types, mappings, and filtering constants — shared between CamStack app and proxy.\n *\n * Covers:\n * - Canonical device type normalization (Scrypted PascalCase + HA domains → canonical)\n * - Eligible device type lists for Scrypted and Home Assistant\n * - Unified Device / DeviceCommand / CommandResult interfaces\n * - AssociatedDeviceState shape\n */\n\n// ---------------------------------------------------------------------------\n// Canonical device type\n// ---------------------------------------------------------------------------\n\nexport type CanonicalDeviceType =\n | \"light\"\n | \"switch\"\n | \"cover\"\n | \"lock\"\n | \"alarm\"\n | \"button\"\n | \"select\"\n | \"siren\"\n | \"sensor\"\n | \"entry\"\n | \"media_player\"\n | \"script\";\n\n// ---------------------------------------------------------------------------\n// Raw type → canonical mapping (Scrypted + Home Assistant)\n// ---------------------------------------------------------------------------\n\n/**\n * Maps raw device types from Scrypted (PascalCase) and Home Assistant (lowercase domains)\n * to canonical CamStack device types.\n */\nexport const RAW_TO_CANONICAL: Record<string, CanonicalDeviceType> = {\n // Scrypted PascalCase\n Light: \"light\",\n Switch: \"switch\",\n WindowCovering: \"cover\",\n Lock: \"lock\",\n SecuritySystem: \"alarm\",\n Buttons: \"button\",\n Select: \"select\",\n Siren: \"siren\",\n Sensor: \"sensor\",\n Entry: \"entry\",\n Program: \"script\",\n MediaPlayer: \"media_player\",\n Outlet: \"switch\",\n // Home Assistant lowercase domains\n light: \"light\",\n switch: \"switch\",\n input_boolean: \"switch\",\n cover: \"cover\",\n lock: \"lock\",\n alarm_control_panel: \"alarm\",\n input_button: \"button\",\n button: \"button\",\n input_select: \"select\",\n select: \"select\",\n siren: \"siren\",\n sensor: \"sensor\",\n media_player: \"media_player\",\n script: \"script\",\n};\n\n/**\n * Extended HA domain → type map (includes non-eligible domains for display/categorization).\n * Superset of RAW_TO_CANONICAL for HA-specific domains.\n */\nexport const HA_DOMAIN_TYPE_MAP: Record<string, string> = {\n light: \"light\",\n switch: \"switch\",\n input_boolean: \"switch\",\n cover: \"cover\",\n lock: \"lock\",\n alarm_control_panel: \"alarm\",\n input_button: \"button\",\n button: \"button\",\n input_select: \"select\",\n select: \"select\",\n siren: \"siren\",\n sensor: \"sensor\",\n binary_sensor: \"sensor\",\n media_player: \"media_player\",\n script: \"script\",\n climate: \"climate\",\n camera: \"camera\",\n fan: \"fan\",\n vacuum: \"vacuum\",\n automation: \"automation\",\n scene: \"scene\",\n input_number: \"sensor\",\n person: \"person\",\n device_tracker: \"tracker\",\n weather: \"weather\",\n water_heater: \"climate\",\n};\n\n/**\n * Extended Scrypted type → canonical map (includes non-eligible types for display).\n * Superset of RAW_TO_CANONICAL for Scrypted-specific types.\n */\nexport const SCRYPTED_TYPE_TO_CANONICAL: Record<string, string> = {\n Light: \"light\",\n Switch: \"switch\",\n WindowCovering: \"cover\",\n Lock: \"lock\",\n SecuritySystem: \"alarm\",\n Buttons: \"button\",\n Select: \"select\",\n Siren: \"siren\",\n Sensor: \"sensor\",\n Entry: \"entry\",\n Program: \"script\",\n MediaPlayer: \"media_player\",\n Camera: \"camera\",\n Doorbell: \"doorbell\",\n Fan: \"fan\",\n Outlet: \"switch\",\n};\n\n/** Normalize raw type (Scrypted or HA) to canonical key for filtering and display. */\nexport function getCanonicalDeviceType(rawType: string): CanonicalDeviceType | null {\n const canonical = RAW_TO_CANONICAL[rawType];\n if (canonical) return canonical;\n const lower = rawType.toLowerCase();\n return RAW_TO_CANONICAL[lower] ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Eligible device types (filter lists)\n// ---------------------------------------------------------------------------\n\n/** Scrypted device types eligible for device control in CamStack. PascalCase. */\nexport const ELIGIBLE_SCRYPTED_DEVICE_TYPES = [\n \"Entry\",\n \"Light\",\n \"Switch\",\n \"Lock\",\n \"SecuritySystem\",\n \"Buttons\",\n \"WindowCovering\",\n \"Siren\",\n \"Sensor\",\n \"Select\",\n \"Program\",\n] as const;\n\n/** Set version for O(1) lookup. */\nexport const ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET = new Set<string>(ELIGIBLE_SCRYPTED_DEVICE_TYPES);\n\n/** Home Assistant domains eligible for device control in CamStack. */\nexport const ELIGIBLE_HA_DOMAINS = [\n \"light\",\n \"switch\",\n \"input_boolean\",\n \"cover\",\n \"lock\",\n \"alarm_control_panel\",\n \"input_button\",\n \"button\",\n \"input_select\",\n \"select\",\n \"siren\",\n \"media_player\",\n \"script\",\n] as const;\n\n/** Set version for O(1) lookup. */\nexport const ELIGIBLE_HA_DOMAINS_SET = new Set<string>(ELIGIBLE_HA_DOMAINS);\n\n// ---------------------------------------------------------------------------\n// Device command action type\n// ---------------------------------------------------------------------------\n\n/** All device command actions supported by CamStack. */\nexport type DeviceCommandAction =\n | \"turnOn\"\n | \"turnOff\"\n | \"openEntry\"\n | \"closeEntry\"\n | \"stopEntry\"\n | \"lock\"\n | \"unlock\"\n | \"disarm\"\n | \"arm\"\n | \"pressButton\"\n | \"selectOption\"\n | \"volumeUp\"\n | \"volumeDown\"\n | \"volumeMute\";\n\n// ---------------------------------------------------------------------------\n// Associated device state (unified shape)\n// ---------------------------------------------------------------------------\n\n/**\n * State snapshot shape for an associated device.\n * Used by both app (AssociatedDeviceStateSnapshot) and proxy (Device.state).\n */\nexport interface AssociatedDeviceState {\n name?: string;\n type?: string;\n interfaces?: string[];\n on?: boolean;\n hasBrightnessControl?: boolean;\n brightness?: number;\n lockState?: string;\n securitySystemState?: { mode?: string; supportedModes?: string[] };\n buttons?: string[];\n entryOpen?: boolean | \"jammed\";\n value?: string;\n options?: string[];\n volumeLevel?: number;\n isVolumeMuted?: boolean;\n coverSupportsStop?: boolean;\n coverPosition?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Device interfaces (from proxy integrations/types.ts)\n// ---------------------------------------------------------------------------\n\n/** A device from an automation system (Scrypted, Home Assistant, etc.). */\nexport interface Device {\n id: string;\n name: string;\n /**\n * Canonical device type (lowercase): \"light\", \"switch\", \"cover\", \"lock\",\n * \"alarm\", \"button\", \"select\", \"siren\", \"sensor\", \"entry\", \"media_player\",\n * \"script\", \"camera\", \"nvr\".\n */\n type: string;\n /** Original type from the source system (e.g. Scrypted \"WindowCovering\", HA domain \"cover\"). */\n rawType?: string;\n /** Provider that owns this device. */\n providerId?: string;\n model?: string;\n manufacturer?: string;\n firmware?: string;\n online: boolean;\n interfaces: string[];\n /** Current state (key-value pairs, device-type-dependent). See AssociatedDeviceState. */\n state: Record<string, unknown>;\n /** Available actions for this device (defined by the provider). */\n actions?: DeviceAction[];\n /** Child devices (e.g. NVR → camera channels). */\n children?: Device[];\n}\n\n/** A command to send to a device. */\nexport interface DeviceCommand {\n deviceId: string;\n command: string;\n params?: Record<string, unknown>;\n}\n\n/** Result of a command execution. */\nexport interface CommandResult {\n success: boolean;\n error?: string;\n state?: Record<string, unknown>;\n}\n\n/** Device source type (integration that provides the device). */\nexport type AssociatedDeviceSource = \"scrypted\" | \"ha\";\n\n/** Eligible device summary (for device picker UI). */\nexport interface EligibleDevice {\n id: string;\n name: string;\n type: string;\n source: AssociatedDeviceSource;\n}\n\n// ─── Device Actions (provider-defined) ──────────────────────────\n\n/**\n * An action that can be performed on a device.\n * Actions are declared by the provider and rendered dynamically in the UI.\n */\nexport interface DeviceAction {\n /** Unique action ID (e.g. \"toggle\", \"setBrightness\", \"ptzMove\", \"reboot\"). */\n id: string;\n /** Display label (e.g. \"Toggle\", \"Set Brightness\", \"Move Camera\"). */\n label: string;\n /** Action category for UI grouping. */\n category?: \"power\" | \"control\" | \"ptz\" | \"media\" | \"system\" | \"custom\";\n /** Icon hint (lucide icon name or emoji). */\n icon?: string;\n /** Parameters this action accepts. */\n params?: DeviceActionParam[];\n /** Whether this action is destructive (shown with warning). */\n destructive?: boolean;\n}\n\n/** A parameter for a device action. */\nexport interface DeviceActionParam {\n /** Parameter key. */\n key: string;\n /** Display label. */\n label: string;\n /** Type for UI rendering. */\n type: \"boolean\" | \"number\" | \"string\" | \"select\" | \"slider\";\n /** Default value. */\n default?: unknown;\n /** For select type: available options. */\n options?: { value: string; label: string }[];\n /** For slider/number: min/max/step. */\n min?: number;\n max?: number;\n step?: number;\n}\n","/**\n * Feature capability matrix — shared between CamStack app and proxy.\n *\n * Maps each app feature to:\n * - Which source types (Frigate, Scrypted, RTSP) support it\n * - Which platforms (iOS, Android, Web, Scrypted embed) it works on\n * - Whether the camstack-server backend is required\n * - The ICameraSource method that enables the feature\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type FeatureId =\n | \"liveStream\"\n | \"multiResolution\"\n | \"motion\"\n | \"objectDetection\"\n | \"audioVolume\"\n | \"audioVolumes\"\n | \"ptz\"\n | \"intercom\"\n | \"deviceStatus\"\n | \"timeline\"\n | \"clusteredTimeline\"\n | \"detectionClasses\"\n | \"videoClips\"\n | \"nvrPlayback\"\n | \"nvrScrub\"\n | \"recordingThumbnail\"\n | \"nvrSeekToLive\";\n\nexport type PlatformId = \"ios\" | \"android\" | \"web\" | \"scryptedEmbed\";\n\nexport type SourceType = \"frigate\" | \"scrypted\" | \"rtsp\";\n\nexport interface FeatureEntry {\n id: FeatureId;\n label: string;\n /** Which source types support this feature. */\n sources: Partial<Record<SourceType, boolean>>;\n /** Platform availability overrides. When omitted, all platforms are supported. */\n platforms?: Partial<Record<PlatformId, boolean>>;\n /** True when this feature needs the camstack-server backend. */\n requiresBackend?: boolean;\n /** The ICameraSource method name that enables this feature at runtime. */\n adapterMethod?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Matrix\n// ---------------------------------------------------------------------------\n\nexport const FEATURE_MATRIX: FeatureEntry[] = [\n {\n id: \"liveStream\",\n label: \"Live Stream\",\n sources: { frigate: true, scrypted: true, rtsp: true },\n adapterMethod: \"getLiveStream\",\n },\n {\n id: \"multiResolution\",\n label: \"Multi-Resolution\",\n sources: { frigate: true, scrypted: true, rtsp: false },\n adapterMethod: \"getResolutions\",\n },\n {\n id: \"motion\",\n label: \"Motion Detection\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"getMotion\",\n },\n {\n id: \"objectDetection\",\n label: \"Object Detection\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"getObjectDetections\",\n },\n {\n id: \"audioVolume\",\n label: \"Audio Level\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"getAudioVolume\",\n },\n {\n id: \"audioVolumes\",\n label: \"Audio Volumes (dBFS)\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"getAudioVolumes\",\n },\n {\n id: \"ptz\",\n label: \"PTZ Control\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"getPTZ\",\n },\n {\n id: \"intercom\",\n label: \"Intercom (Mic)\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"getIntercomSupport\",\n },\n {\n id: \"deviceStatus\",\n label: \"Device Status\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"getStatus\",\n },\n {\n id: \"timeline\",\n label: \"Detection Timeline\",\n sources: { frigate: true, scrypted: true, rtsp: false },\n adapterMethod: \"getCameraDayData\",\n },\n {\n id: \"clusteredTimeline\",\n label: \"Clustered Timeline\",\n sources: { frigate: true, scrypted: true, rtsp: false },\n requiresBackend: true,\n adapterMethod: \"getClusteredDayData\",\n },\n {\n id: \"detectionClasses\",\n label: \"Detection Classes\",\n sources: { frigate: true, scrypted: true, rtsp: false },\n adapterMethod: \"getDetectionClasses\",\n },\n {\n id: \"videoClips\",\n label: \"Video Clips\",\n sources: { frigate: true, scrypted: true, rtsp: false },\n adapterMethod: \"getVideoClips\",\n },\n {\n id: \"nvrPlayback\",\n label: \"NVR Playback\",\n sources: { frigate: true, scrypted: true, rtsp: false },\n adapterMethod: \"getNvrPlaybackSupported\",\n },\n {\n id: \"nvrScrub\",\n label: \"NVR Scrub/Seek\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"seekRecordingStream\",\n },\n {\n id: \"recordingThumbnail\",\n label: \"Recording Thumbnails\",\n sources: { frigate: true, scrypted: true, rtsp: false },\n adapterMethod: \"getRecordingStreamThumbnail\",\n },\n {\n id: \"nvrSeekToLive\",\n label: \"Seek to Live\",\n sources: { frigate: false, scrypted: true, rtsp: false },\n adapterMethod: \"seekNvrToLive\",\n },\n];\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Check if a feature is available for a given source type and platform. */\nexport function isFeatureAvailable(\n featureId: FeatureId,\n source: SourceType,\n platform: PlatformId,\n): boolean {\n const entry = FEATURE_MATRIX.find((f) => f.id === featureId);\n if (!entry) return false;\n if (!entry.sources[source]) return false;\n if (entry.platforms && entry.platforms[platform] === false) return false;\n return true;\n}\n\n/** Get all features supported by a source type. */\nexport function getSourceFeatures(source: SourceType): FeatureEntry[] {\n return FEATURE_MATRIX.filter((f) => f.sources[source]);\n}\n\n/** Get all features that require the backend proxy. */\nexport function getBackendRequiredFeatures(): FeatureEntry[] {\n return FEATURE_MATRIX.filter((f) => f.requiresBackend);\n}\n","/**\n * Adaptive Stream Session — negotiates optimal stream quality with the proxy.\n *\n * The client reports viewport size and network conditions periodically.\n * The proxy selects the best stream from the provider's available streams\n * and instructs the client to switch via go2rtc stream renegotiation.\n *\n * Flow:\n * 1. Client opens session → sends initial constraints (viewport, network)\n * 2. Server evaluates available streams → picks optimal → responds with stream assignment\n * 3. Client connects to assigned go2rtc stream via WebRTC WHEP\n * 4. Periodically, client reports updated stats → server may switch stream\n * 5. On switch: client tears down old PC, creates new PC to new go2rtc stream\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Client-reported constraints for stream selection. */\nexport interface StreamConstraints {\n /** Viewport width in CSS pixels. */\n viewportWidth: number;\n /** Viewport height in CSS pixels. */\n viewportHeight: number;\n /** Device pixel ratio (retina = 2+). */\n pixelRatio?: number;\n /** Estimated downlink bandwidth in Mbps (from navigator.connection or WebRTC stats). */\n bandwidthMbps?: number;\n /** Round-trip time in ms (from WebRTC stats). */\n rttMs?: number;\n /** Packet loss ratio 0-1 (from WebRTC stats). */\n packetLoss?: number;\n /** Whether the client is on a cellular network. */\n isCellular?: boolean;\n /** Whether the client prefers low latency over quality. */\n preferLowLatency?: boolean;\n /** Maximum resolution the client wants (e.g., from a user setting). */\n maxResolution?: \"auto\" | \"high\" | \"medium\" | \"low\";\n}\n\n/** Server-assigned stream for the client. */\nexport interface StreamAssignment {\n /** go2rtc stream name to connect to. */\n streamName: string;\n /** Stream profile (main/sub/ext). */\n profile: string;\n /** Transport type (native/rtsp/rtmp). */\n transport: string;\n /** Human-readable label. */\n label: string;\n /** Stream metadata (if known). */\n metadata?: {\n width?: number;\n height?: number;\n fps?: number;\n bitrate?: number;\n codec?: string;\n };\n /** Reason for this selection. */\n reason: string;\n}\n\n/** Stream selection request from client → server. */\nexport interface StreamSelectionRequest {\n providerId: string;\n camera: string;\n constraints: StreamConstraints;\n /** Current stream (for comparison — only switch if significantly better). */\n currentStreamName?: string;\n}\n\n/** Stream selection response from server → client. */\nexport interface StreamSelectionResponse {\n assignment: StreamAssignment;\n /** Available alternatives the client can manually pick. */\n alternatives: StreamAssignment[];\n /** Seconds until next automatic re-evaluation. */\n nextEvalSeconds: number;\n}\n\n// ---------------------------------------------------------------------------\n// Stream selection algorithm (can run on server or client)\n// ---------------------------------------------------------------------------\n\n/**\n * Select the optimal stream from available options based on client constraints.\n * This is a pure function — no side effects.\n */\nexport function selectOptimalStream(\n streams: Array<{\n streamName: string;\n profile: string;\n transport: string;\n label: string;\n metadata?: { width?: number; height?: number; fps?: number; bitrate?: number; codec?: string };\n }>,\n constraints: StreamConstraints,\n defaultTransport?: string,\n): StreamAssignment | null {\n if (streams.length === 0) return null;\n\n const viewportPixels = constraints.viewportWidth * constraints.viewportHeight * (constraints.pixelRatio ?? 1);\n const bw = constraints.bandwidthMbps ?? 100; // Assume good bandwidth if unknown\n const loss = constraints.packetLoss ?? 0;\n const rtt = constraints.rttMs ?? 50;\n\n // Determine target quality tier\n let targetTier: \"high\" | \"medium\" | \"low\" = \"high\";\n\n if (constraints.maxResolution === \"low\" || constraints.isCellular || bw < 1 || loss > 0.05) {\n targetTier = \"low\";\n } else if (constraints.maxResolution === \"medium\" || bw < 5 || viewportPixels < 500_000 || rtt > 200) {\n targetTier = \"medium\";\n } else if (constraints.maxResolution === \"high\" || viewportPixels > 2_000_000) {\n targetTier = \"high\";\n }\n\n // Map profile to tier\n const PROFILE_TIER: Record<string, \"high\" | \"medium\" | \"low\"> = {\n main: \"high\",\n sub: \"medium\",\n ext: \"low\",\n };\n\n // Prefer the default transport, fallback to any\n const preferTransport = defaultTransport ?? \"native\";\n\n // Score each stream\n const scored = streams.map((s) => {\n let score = 0;\n const streamTier = PROFILE_TIER[s.profile] ?? \"medium\";\n\n // Profile match: exact tier match is best\n if (streamTier === targetTier) score += 100;\n else if (\n (targetTier === \"high\" && streamTier === \"medium\") ||\n (targetTier === \"medium\" && streamTier === \"high\") ||\n (targetTier === \"medium\" && streamTier === \"low\") ||\n (targetTier === \"low\" && streamTier === \"medium\")\n ) score += 50; // Adjacent tier\n // else: 0 (far tier)\n\n // Transport preference\n if (s.transport === preferTransport) score += 30;\n else if (s.transport === \"native\") score += 20; // Native always good\n else if (s.transport === \"rtsp\") score += 15;\n else if (s.transport === \"rtmp\") score += 10;\n\n // Resolution match (if metadata available)\n if (s.metadata?.width && s.metadata?.height) {\n const streamPixels = s.metadata.width * s.metadata.height;\n const ratio = streamPixels / Math.max(viewportPixels, 1);\n // Ideal: stream slightly larger than viewport (1.0-1.5x)\n if (ratio >= 0.8 && ratio <= 2.0) score += 25;\n else if (ratio >= 0.5 && ratio <= 3.0) score += 15;\n // Penalize very oversized streams on low bandwidth\n if (ratio > 4 && bw < 10) score -= 20;\n }\n\n // Codec preference: H.264 better for compatibility, H.265 better for bandwidth\n if (s.metadata?.codec) {\n if (bw < 5 && s.metadata.codec.includes(\"265\")) score += 10; // H.265 saves bandwidth\n if (bw >= 10 && s.metadata.codec.includes(\"264\")) score += 5; // H.264 more compatible\n }\n\n return { ...s, score, targetTier };\n });\n\n // Sort by score descending\n scored.sort((a, b) => b.score - a.score);\n const best = scored[0]!;\n\n const tierNames: Record<string, string> = { high: \"main\", medium: \"sub\", low: \"ext\" };\n\n return {\n streamName: best.streamName,\n profile: best.profile,\n transport: best.transport,\n label: best.label,\n metadata: best.metadata,\n reason: `${best.targetTier} quality → ${best.profile}/${best.transport} (score: ${best.score})`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Re-evaluation interval logic\n// ---------------------------------------------------------------------------\n\n/** Determine seconds until next re-evaluation based on stability. */\nexport function getNextEvalInterval(\n constraints: StreamConstraints,\n wasSwitch: boolean,\n): number {\n if (wasSwitch) return 10; // Re-check soon after a switch\n if (constraints.isCellular) return 15; // Cellular: check frequently\n if ((constraints.packetLoss ?? 0) > 0.02) return 15; // Lossy: check frequently\n return 30; // Stable: check every 30s\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACUA,oBAA8E;AAC9E,uBAAsB;AA8Bf,IAAMA,gBAAN,MAAMA;EAzCb,OAyCaA;;;;EAEFC;EACAC;EACDC;EACAC,WAAqD;EAE7D,YAAYC,QAA6B;AACvC,SAAKH,YAAYG,OAAOH,UAAUI,QAAQ,QAAQ,EAAA;AAClD,SAAKH,QAAQE,OAAOF;AAEpB,UAAMI,YAAY,OAAOC,WAAW;AACpC,UAAMC,QAAQJ,OAAOK,gBAAgBH;AAErC,UAAMI,UAAU,6BAAA;AACd,YAAMC,IAA4B,CAAC;AACnC,UAAI,KAAKT,OAAO;AACdS,UAAE,eAAA,IAAmB,UAAU,KAAKT,KAAK;MAC3C;AACA,aAAOS;IACT,GANgB;AAQhB,QAAIH,OAAO;AAET,YAAMI,QAAQ,KAAKX,UAAUI,QAAQ,SAAS,IAAA,IAAQ;AACtD,WAAKF,eAAWU,8BAAe;QAC7BC,KAAKF;QACLG,kBAAkB,8BAAO;UACvBb,OAAO,KAAKA;QACd,IAFkB;QAGlBc,cAAc,6BAAM,KAAN;MAChB,CAAA;AAEA,WAAKhB,WAAOiB,gCAAmC;QAC7CC,OAAO;cACLC,sBAAO;YACLC,QAAQ,KAAKjB;YACbkB,aAAaC,iBAAAA;UACf,CAAA;;MAEJ,CAAA;IACF,OAAO;AAEL,WAAKtB,WAAOiB,gCAAmC;QAC7CC,OAAO;cACLK,wBAAS;YACPT,KAAK,GAAG,KAAKb,SAAS;YACtBS;YACAW,aAAaC,iBAAAA;UACf,CAAA;;MAEJ,CAAA;IACF;EACF;;EAGAE,SAAStB,OAAqB;AAC5B,SAAKA,QAAQA;EACf;;EAGAuB,QAAc;AACZ,SAAKtB,UAAUsB,MAAAA;EACjB;;EAIA,MAAMC,MAAMC,UAAkBC,UAAkB;AAC9C,WAAO,KAAK5B,KAAK6B,KAAKH,MAAMI,OAAO;MAAEH;MAAUC;IAAS,CAAA;EAC1D;EAEA,MAAMG,QAAQ;AACZ,WAAO,KAAK/B,KAAK6B,KAAKG,GAAGC,MAAK;EAChC;EAEA,MAAMC,SAAS;AACb,WAAO,KAAKlC,KAAK6B,KAAKK,OAAOJ,OAAM;EACrC;;EAIA,MAAMK,gBAAgB;AACpB,WAAO,KAAKnC,KAAKoC,OAAOC,KAAKJ,MAAK;EACpC;EAEA,MAAMK,kBAAkB;AACtB,WAAO,KAAKtC,KAAKoC,OAAOG,aAAaN,MAAK;EAC5C;;EAIA,MAAMO,gBAAgB;AACpB,WAAO,KAAKxC,KAAKyC,UAAUC,KAAKT,MAAK;EACvC;EAEA,MAAMU,YAAYC,YAAoB;AACpC,WAAO,KAAK5C,KAAKyC,UAAUI,IAAIZ,MAAM;MAAEa,IAAIF;IAAW,CAAA;EACxD;EAEA,MAAMG,cAAcH,YAAoB;AACtC,WAAO,KAAK5C,KAAKyC,UAAUO,MAAMlB,OAAO;MAAEgB,IAAIF;IAAW,CAAA;EAC3D;EAEA,MAAMK,aAAaL,YAAoB;AACrC,WAAO,KAAK5C,KAAKyC,UAAUS,KAAKpB,OAAO;MAAEgB,IAAIF;IAAW,CAAA;EAC1D;EAEA,MAAMO,oBAAoB;AACxB,WAAO,KAAKnD,KAAKoD,eAAeC,kBAAkBpB,MAAK;EACzD;;EAIA,MAAMqB,cAAc;AAClB,WAAO,KAAKtD,KAAKuD,QAAQb,KAAKT,MAAK;EACrC;EAEA,MAAMuB,UAAUC,UAAkB;AAChC,WAAO,KAAKzD,KAAKuD,QAAQV,IAAIZ,MAAM;MAAEa,IAAIW;IAAS,CAAA;EACpD;EAEA,MAAMC,gBAAgBd,YAAoB;AACxC,WAAO,KAAK5C,KAAKuD,QAAQG,gBAAgBzB,MAAM;MAAEW;IAAW,CAAA;EAC9D;EAEA,MAAMe,YAAYf,YAAoBgB,YAAoB;AACxD,WAAO,KAAK5D,KAAKuD,QAAQI,YAAY7B,OAAO;MAAEc;MAAYgB;IAAW,CAAA;EACvE;EAEA,MAAMC,aAAajB,YAAoBxC,QAAiC;AACtE,WAAO,KAAKJ,KAAKuD,QAAQM,aAAa/B,OAAO;MAAEc;MAAYxC;IAAO,CAAA;EACpE;;EAGA0D,iBAAiBC,SAAiBC,WAA2B;AAC3D,WAAO,qBAAqBD,OAAAA,IAAWC,SAAAA;EACzC;;EAIA,MAAMC,aAAa;AACjB,WAAO,KAAKjE,KAAKkE,OAAOxB,KAAKT,MAAK;EACpC;EAEA,MAAMkC,qBAAqBJ,SAAiB;AAC1C,WAAO,KAAK/D,KAAKkE,OAAOE,gBAAgBnC,MAAM;MAAE8B;IAAQ,CAAA;EAC1D;EAEA,MAAMM,eAAeN,SAAiB;AACpC,WAAO,KAAK/D,KAAKkE,OAAOI,UAAUrC,MAAM;MAAE8B;IAAQ,CAAA;EACpD;EAEA,MAAMQ,kBAAkBR,SAAiB3D,QAAiC;AACxE,WAAO,KAAKJ,KAAKkE,OAAOM,aAAa1C,OAAO;MAAEiC;MAAS3D;IAAO,CAAA;EAChE;EAEA,MAAMqE,aAAaV,SAAiBW,SAA2E;AAC7G,WAAO,KAAK1E,KAAKkE,OAAOS,QAAQ1C,MAAM;MAAE8B;MAAS,GAAGW;IAAQ,CAAA;EAC9D;;EAIA,MAAME,iBAAiB;AACrB,WAAO,KAAK5E,KAAK6E,WAAWnC,KAAKT,MAAK;EACxC;EAEA,MAAM6C,aAAaC,OAAeC,YAAoBC,OAAgB;AACpE,WAAO,KAAKjF,KAAK6E,WAAWK,SAASpD,OAAO;MAAEiD;MAAOC;MAAYC;IAAM,CAAA;EACzE;;EAIA,MAAME,gBAAgB;AACpB,WAAO,KAAKnF,KAAKoF,eAAenB,WAAWhC,MAAK;EAClD;EAEA,MAAMoD,kBAAkB5B,UAAkB;AACxC,WAAO,KAAKzD,KAAKoF,eAAeE,YAAYrD,MAAM;MAAEwB;IAAS,CAAA;EAC/D;;EAIA,MAAM8B,aAAa;AACjB,WAAO,KAAKvF,KAAKwF,OAAOD,WAAWtD,MAAK;EAC1C;EAEA,MAAMwD,aAAaC,SAAiBC,MAA+B;AACjE,WAAO,KAAK3F,KAAKwF,OAAOC,aAAa3D,OAAO;MAAE8D,YAAYF;MAASG,OAAOF;IAAK,CAAA;EACjF;EAEA,MAAMG,iBAAiB;AACrB,WAAO,KAAK9F,KAAK+F,MAAMC,YAAY/D,MAAK;EAC1C;;EAIA,MAAMgE,mBAAmB;AACvB,WAAO,KAAKjG,KAAKoF,eAAenB,WAAWhC,MAAK;EAClD;EAEA,MAAMiE,kBAAkBzC,UAAkB;AACxC,WAAO,KAAKzD,KAAKoF,eAAeE,YAAYrD,MAAM;MAAEwB;IAAS,CAAA;EAC/D;EAEA,MAAM0C,kBAAkB1C,UAAkBrD,QAA+C;AACvF,WAAO,KAAKJ,KAAKoF,eAAegB,YAAYtE,OAAO;MAAE2B;MAAUrD;IAAsB,CAAA;EACvF;EAEA,MAAMiG,uBAAuBjG,QAA+C;AAC1E,WAAO,KAAKJ,KAAKoF,eAAekB,iBAAiBrE,MAAM7B,MAAAA;EACzD;EAEA,MAAMmG,qBAAqBxC,SAAiB;AAC1C,WAAO,KAAK/D,KAAKoF,eAAef,eAAepC,MAAM;MAAE8B;IAAQ,CAAA;EACjE;EAEA,MAAMyC,qBAAqBzC,SAAiB3D,QAAiC;AAC3E,WAAO,KAAKJ,KAAKoF,eAAeqB,eAAe3E,OAAO;MAAEiC;MAAS3D;IAAO,CAAA;EAC1E;;EAIA,MAAMsG,qBAAqB;AACzB,WAAO,KAAK1G,KAAK2G,aAAaC,aAAa3E,MAAK;EAClD;EAEA,MAAM4E,2BAA2B;AAC/B,WAAO,KAAK7G,KAAK2G,aAAa1C,WAAWhC,MAAK;EAChD;EAEA,MAAM6E,qBAAqBC,aAAqBC,SAAkB;AAChE,WAAO,KAAKhH,KAAK2G,aAAaM,eAAenF,OAAO;MAAEiF;MAAaC;IAAQ,CAAA;EAC7E;EAEA,MAAME,uBAAuBH,aAAqB;AAChD,WAAO,KAAK/G,KAAK2G,aAAaQ,iBAAiBrF,OAAO;MAAEiF;IAAY,CAAA;EACtE;;EAIA,MAAMK,mBAAmB3D,UAAkB;AACzC,WAAO,KAAKzD,KAAKqH,UAAU/C,UAAUrC,MAAM;MAAEwB;IAAS,CAAA;EACxD;EAEA,MAAM6D,mBAAmB7D,UAAkB;AACzC,WAAO,KAAKzD,KAAKqH,UAAUE,UAAUtF,MAAM;MAAEwB;IAAS,CAAA;EACxD;EAEA,MAAM+D,yBAAyB/D,UAAkB;AAC/C,WAAO,KAAKzD,KAAKqH,UAAUI,gBAAgBxF,MAAM;MAAEwB;IAAS,CAAA;EAC9D;EAEA,MAAMiE,qBAAqBjE,UAAkBkE,UAAkBC,WAAmBC,SAAiB;AACjG,WAAO,KAAK7H,KAAKqH,UAAUS,YAAY7F,MAAM;MAAEwB;MAAUkE;MAAUC;MAAWC;IAAQ,CAAA;EACxF;;EAIA,MAAME,UAAUtE,UAAkBiB,SAA+C;AAC/E,WAAO,KAAK1E,KAAKgI,OAAO/F,MAAMA,MAAM;MAAEwB;MAAU,GAAGiB;IAAQ,CAAA;EAC7D;;EAIA,MAAMC,QAAQD,SAA6H;AACzI,WAAO,KAAK1E,KAAKiI,KAAKhG,MAAMA,MAAMyC,WAAW,CAAC,CAAA;EAChD;;EAIA,MAAMwD,SAASC,MAAcC,OAAmJ;AAC9K,WAAO,KAAKpI,KAAKqI,KAAKC,QAAQxG,OAAO;MAAEqG;MAAMC,OAAOA,SAAS;QAAEG,MAAM;MAAkB;IAAE,CAAA;EAC3F;;EAIA,MAAMC,YAAY;AAChB,WAAO,KAAKxI,KAAKyI,MAAM/F,KAAKT,MAAK;EACnC;EAEA,MAAMyG,WAAW/G,UAAkBC,UAAkB+G,MAA0C;AAC7F,WAAO,KAAK3I,KAAKyI,MAAMG,OAAO9G,OAAO;MAAEH;MAAUC;MAAU+G;IAAK,CAAA;EAClE;;EAIA,MAAME,oBAAoBpF,UAAmB;AAC3C,WAAO,KAAKzD,KAAK8I,QAAQC,gBAAgB9G,MAAM;MAAEwB;IAAS,CAAA;EAC5D;;EAIA,MAAMuF,gBAAgB;AACpB,WAAO,KAAKhJ,KAAKiJ,UAAUD,cAAc/G,MAAK;EAChD;EAEA,MAAMiH,eAAetG,YAAoB;AACvC,WAAO,KAAK5C,KAAKiJ,UAAUC,eAAepH,OAAO;MAAEgB,IAAIF;IAAW,CAAA;EACpE;EAEA,MAAMuG,gBAAgBvG,YAAoB;AACxC,WAAO,KAAK5C,KAAKiJ,UAAUE,gBAAgBrH,OAAO;MAAEgB,IAAIF;IAAW,CAAA;EACrE;;;EAKA,MAAMwG,kBAAkBC,SAA2B;AACjD,WAAO,KAAKrJ,KAAKsJ,SAASC,UAAUtH,MAAM;MAAEoH;IAAQ,CAAA;EACtD;EAEA,MAAMG,YAAYH,SAA0B;AAC1C,WAAO,KAAKrJ,KAAKsJ,SAASzG,IAAIZ,MAAM;MAAEoH;IAAQ,CAAA;EAChD;EAEA,MAAMI,eAAeJ,SAA0BK,MAA+B;AAC5E,WAAO,KAAK1J,KAAKsJ,SAASK,OAAO7H,OAAO;MAAEuH;MAASK;IAAK,CAAA;EAC1D;AACF;AAMO,SAASE,oBAAoBxJ,QAA2B;AAC7D,SAAO,IAAIL,cAAcK,MAAAA;AAC3B;AAFgBwJ;;;ACpWT,IAAKC,iBAAAA,0BAAAA,iBAAAA;;;;;;;;;;;SAAAA;;AAiBL,IAAMC,gBAAgB;;EAE3B;EAAW;EAAO;EAAO;EAAS;EAAS;EAAO;EAAY;EAC9D;EAAS;EAAW;EAAS;EAAU;EAAQ;EAAQ;EACvD;EAAQ;EAAS;EAAO;EACxB;EAAQ;EAAS;EACjB;EAAS;EAAU;;AAGd,IAAMC,gBAAgB;;EAE3B;EAAU;EAAc;EAAS;EAAU;EAAW;EAAS;EAC/D;EAAQ;EAAQ;EAAQ;;AAGnB,IAAMC,iBAAiB;;EAE5B;EAAO;EAAS;EAAO;EAAc;EAAW;EAChD;EAAa;EAAc;EAC3B;EAAS;EAAU;EACnB;EAAY;EAAQ;EAAQ;;AAGvB,IAAMC,cAAc;;EAEzB;EAAQ;EAAQ;EAAS;EAAQ;EACjC;EAAY;EAAa;EAAS;EAAQ;EAAU;EACpD;EAAa;EAAa;EAC1B;EAAQ;EAAS;EAAY;EAC7B;EAAW;EAAc;EAAe;EAAS;EACjD;EAAmB;;AAGd,IAAMC,sBAAsB;;EAEjC;EAAiB;EAAe;EAAc;EAC9C;EAAmB;EAAgB;EACnC;EAAgB;EAAmB;EAAe;EAClD;EAAgB;EAAgB;EAChC;EAAe;EAAc;EAAiB;EAC9C;EAAiB;EAAkB;;AAG9B,IAAMC,gBAAgB;;EAAwB;EAAY;;AAE1D,IAAMC,iBAAiB;;EAAyB;;AAEhD,IAAMC,eAAyB;;;AAG/B,IAAMC,oBAAoB;EAC/B;EAAU;EAAU;EAAY;EAAQ;EAAU;EAAS;EAAc;EAAY;EACrF;EAAU;EAAO;EAAQ;EAAW;EAAS;EAAS;EAAU;EAAiB;EAAW;EAC5F;EAAS;EAAS;EAAa;EAAa;EAAU;EAAW;EAAQ;EAAQ;EAAS;EAC1F;EAAmB;EAAU;EAAS;EAAY;EAAY;EAAW;EAAS;EAClF;EAAQ;EAAO;EAAQ;EAAW;EAAY;EAAe;EAAQ;EAAQ;EAAQ;EACrF;EAAQ;EAAa;EAAY;EAAa;EAAQ;EAAQ;EAAS;EAAS;EAChF;EAAS;EAAO;;AAGX,IAAMC,kBAAkB;;EAA0B;;AAGlD,IAAMC,qBAAqB;EAChC;EAAQ;EAAU;EAAS;EAC3B;EAAQ;EAAQ;EAAa;EAAW;;AAOnC,IAAMC,6BAA6D;EACxE,GAAGX,cAAcY,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAwB,IAAI,CAAC,CAAA;EACrF,GAAGb,cAAcW,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAwB,IAAI,CAAC,CAAA;EACrF,GAAGZ,eAAeU,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAyB,IAAI,CAAC,CAAA;EACvF,GAAGT,cAAcO,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAwB,IAAI,CAAC,CAAA;EACrF,GAAGR,eAAeM,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAyB,IAAI,CAAC,CAAA;EACvF,GAAGX,YAAYS,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAsB,IAAI,CAAC,CAAA;EACjF,GAAGV,oBAAoBQ,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAuB,IAAI,CAAC,CAAA;EAC1F,GAAGP,aAAaK,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAuB,IAAI,CAAC,CAAA;EACnF,GAAGN,kBAAkBI,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAuB,IAAI,CAAC,CAAA;EACxF,GAAGL,gBAAgBG,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAA0B,IAAI,CAAC,CAAA;EACzF,GAAGJ,mBAAmBE,OAAO,CAACC,KAAKC,UAAU;IAAE,GAAGD;IAAK,CAACC,IAAAA,GAAK;EAAwB,IAAI,CAAC,CAAA;AAC5F;AAMO,IAAMC,kBAAkB,wBAACC,MAAcb,YAAYc,SAASD,CAAAA,GAApC;AACxB,IAAME,mBAAmB,wBAACF,MAAcZ,oBAAoBa,SAASD,CAAAA,GAA5C;AACzB,IAAMG,oBAAoB,wBAACH,MAAchB,cAAciB,SAASD,CAAAA,GAAtC;AAC1B,IAAMI,oBAAoB,wBAACJ,MAAcf,cAAcgB,SAASD,CAAAA,GAAtC;AAC1B,IAAMK,qBAAqB,wBAACL,MAAcd,eAAee,SAASD,CAAAA,GAAvC;AAC3B,IAAMM,oBAAoB,wBAACN,MAAcX,cAAcY,SAASD,CAAAA,GAAtC;AAC1B,IAAMO,sBAAsB,wBAACP,MAAcP,gBAAgBQ,SAASD,CAAAA,GAAxC;AAC5B,IAAMQ,qBAAqB,wBAACR,MAAcV,eAAeW,SAASD,CAAAA,GAAvC;AAC3B,IAAMS,mBAAmB,wBAACT,MAC/BT,aAAaU,SAASD,CAAAA,KAAMR,kBAAkBS,SAASD,CAAAA,GADzB;AAEzB,IAAMU,yBAAyB,wBAACV,MAAcN,mBAAmBO,SAASD,CAAAA,GAA3C;AAC/B,IAAMW,mBAAmB,wBAACX,MAAcD,gBAAgBC,CAAAA,KAAME,iBAAiBF,CAAAA,GAAtD;AAEzB,IAAMY,iBAAiB,wBAACC,cAC7BlB,2BAA2BkB,SAAAA,GADC;AAGvB,IAAMC,0BAA0B,wBAACC,QAAAA;AACtC,QAAM,EAAEF,UAAS,IAAKE;AACtB,QAAMC,UAA0C;IAC9C,CAAA,MAAA,GAAqB;IACrB,CAAA,OAAA,GAAsB;EACxB;AACA,QAAMC,cAActB,2BAA2BkB,SAAAA;AAC/C,MAAII,eAAeA,gBAAgBJ,UAAW,QAAOI;AACrD,SAAOD,QAAQH,SAAAA;AACjB,GATuC;AAehC,IAAMK,0BAA0BC,OAAOC,OAAOrC,cAAAA;AAG9C,IAAMsC,0BAA0BH,wBAAwBI,OAC7D,CAACtB,MAAMA,MAAAA,QAAAA;AAUF,IAAMuB,2BAAqC;;;;;AAO3C,IAAMC,4BAAsC;KAC9CD;;;;;;;AASE,IAAME,sBAAgC;KAAIJ;;AAG1C,SAASK,4BACdC,QACAC,eAAwB;AAExB,UAAQD,QAAAA;IACN,KAAK;AACH,aAAOJ;IACT,KAAK;AACH,aAAOC;IACT,KAAK;AACH,aAAOC;IACT,KAAK;AACH,aAAOG,eAAeC,SAASD,gBAAgBP;IACjD;AACE,aAAOA;EACX;AACF;AAhBgBK;;;ACjJT,IAAMI,mBAAwD;;EAEnEC,OAAO;EACPC,QAAQ;EACRC,gBAAgB;EAChBC,MAAM;EACNC,gBAAgB;EAChBC,SAAS;EACTC,QAAQ;EACRC,OAAO;EACPC,QAAQ;EACRC,OAAO;EACPC,SAAS;EACTC,aAAa;EACbC,QAAQ;;EAERC,OAAO;EACPC,QAAQ;EACRC,eAAe;EACfC,OAAO;EACPC,MAAM;EACNC,qBAAqB;EACrBC,cAAc;EACdC,QAAQ;EACRC,cAAc;EACdC,QAAQ;EACRC,OAAO;EACPC,QAAQ;EACRC,cAAc;EACdC,QAAQ;AACV;AAMO,IAAMC,qBAA6C;EACxDd,OAAO;EACPC,QAAQ;EACRC,eAAe;EACfC,OAAO;EACPC,MAAM;EACNC,qBAAqB;EACrBC,cAAc;EACdC,QAAQ;EACRC,cAAc;EACdC,QAAQ;EACRC,OAAO;EACPC,QAAQ;EACRI,eAAe;EACfH,cAAc;EACdC,QAAQ;EACRG,SAAS;EACTC,QAAQ;EACRC,KAAK;EACLC,QAAQ;EACRC,YAAY;EACZC,OAAO;EACPC,cAAc;EACdC,QAAQ;EACRC,gBAAgB;EAChBC,SAAS;EACTC,cAAc;AAChB;AAMO,IAAMC,6BAAqD;EAChExC,OAAO;EACPC,QAAQ;EACRC,gBAAgB;EAChBC,MAAM;EACNC,gBAAgB;EAChBC,SAAS;EACTC,QAAQ;EACRC,OAAO;EACPC,QAAQ;EACRC,OAAO;EACPC,SAAS;EACTC,aAAa;EACb8B,QAAQ;EACRC,UAAU;EACVC,KAAK;EACL/B,QAAQ;AACV;AAGO,SAASgC,uBAAuBC,SAAe;AACpD,QAAMC,YAAY/C,iBAAiB8C,OAAAA;AACnC,MAAIC,UAAW,QAAOA;AACtB,QAAMC,QAAQF,QAAQG,YAAW;AACjC,SAAOjD,iBAAiBgD,KAAAA,KAAU;AACpC;AALgBH;AAYT,IAAMK,iCAAiC;EAC5C;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIK,IAAMC,qCAAqC,IAAIC,IAAYF,8BAAAA;AAG3D,IAAMG,sBAAsB;EACjC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIK,IAAMC,0BAA0B,IAAIF,IAAYC,mBAAAA;;;ACtHhD,IAAME,iBAAiC;EAC5C;IACEC,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAMC,UAAU;MAAMC,MAAM;IAAK;IACrDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAMC,UAAU;MAAMC,MAAM;IAAM;IACtDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAMC,UAAU;MAAMC,MAAM;IAAM;IACtDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAMC,UAAU;MAAMC,MAAM;IAAM;IACtDE,iBAAiB;IACjBD,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAMC,UAAU;MAAMC,MAAM;IAAM;IACtDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAMC,UAAU;MAAMC,MAAM;IAAM;IACtDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAMC,UAAU;MAAMC,MAAM;IAAM;IACtDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAMC,UAAU;MAAMC,MAAM;IAAM;IACtDC,eAAe;EACjB;EACA;IACEN,IAAI;IACJC,OAAO;IACPC,SAAS;MAAEC,SAAS;MAAOC,UAAU;MAAMC,MAAM;IAAM;IACvDC,eAAe;EACjB;;AAQK,SAASE,mBACdC,WACAC,QACAC,UAAoB;AAEpB,QAAMC,QAAQb,eAAec,KAAK,CAACC,MAAMA,EAAEd,OAAOS,SAAAA;AAClD,MAAI,CAACG,MAAO,QAAO;AACnB,MAAI,CAACA,MAAMV,QAAQQ,MAAAA,EAAS,QAAO;AACnC,MAAIE,MAAMG,aAAaH,MAAMG,UAAUJ,QAAAA,MAAc,MAAO,QAAO;AACnE,SAAO;AACT;AAVgBH;AAaT,SAASQ,kBAAkBN,QAAkB;AAClD,SAAOX,eAAekB,OAAO,CAACH,MAAMA,EAAEZ,QAAQQ,MAAAA,CAAO;AACvD;AAFgBM;AAKT,SAASE,6BAAAA;AACd,SAAOnB,eAAekB,OAAO,CAACH,MAAMA,EAAEP,eAAe;AACvD;AAFgBW;;;AC9FT,SAASC,oBACdC,SAOAC,aACAC,kBAAyB;AAEzB,MAAIF,QAAQG,WAAW,EAAG,QAAO;AAEjC,QAAMC,iBAAiBH,YAAYI,gBAAgBJ,YAAYK,kBAAkBL,YAAYM,cAAc;AAC3G,QAAMC,KAAKP,YAAYQ,iBAAiB;AACxC,QAAMC,OAAOT,YAAYU,cAAc;AACvC,QAAMC,MAAMX,YAAYY,SAAS;AAGjC,MAAIC,aAAwC;AAE5C,MAAIb,YAAYc,kBAAkB,SAASd,YAAYe,cAAcR,KAAK,KAAKE,OAAO,MAAM;AAC1FI,iBAAa;EACf,WAAWb,YAAYc,kBAAkB,YAAYP,KAAK,KAAKJ,iBAAiB,OAAWQ,MAAM,KAAK;AACpGE,iBAAa;EACf,WAAWb,YAAYc,kBAAkB,UAAUX,iBAAiB,KAAW;AAC7EU,iBAAa;EACf;AAGA,QAAMG,eAA0D;IAC9DC,MAAM;IACNC,KAAK;IACLC,KAAK;EACP;AAGA,QAAMC,kBAAkBnB,oBAAoB;AAG5C,QAAMoB,SAAStB,QAAQuB,IAAI,CAACC,MAAAA;AAC1B,QAAIC,QAAQ;AACZ,UAAMC,aAAaT,aAAaO,EAAEG,OAAO,KAAK;AAG9C,QAAID,eAAeZ,WAAYW,UAAS;aAErCX,eAAe,UAAUY,eAAe,YACxCZ,eAAe,YAAYY,eAAe,UAC1CZ,eAAe,YAAYY,eAAe,SAC1CZ,eAAe,SAASY,eAAe,SACxCD,UAAS;AAIX,QAAID,EAAEI,cAAcP,gBAAiBI,UAAS;aACrCD,EAAEI,cAAc,SAAUH,UAAS;aACnCD,EAAEI,cAAc,OAAQH,UAAS;aACjCD,EAAEI,cAAc,OAAQH,UAAS;AAG1C,QAAID,EAAEK,UAAUC,SAASN,EAAEK,UAAUE,QAAQ;AAC3C,YAAMC,eAAeR,EAAEK,SAASC,QAAQN,EAAEK,SAASE;AACnD,YAAME,QAAQD,eAAeE,KAAKC,IAAI/B,gBAAgB,CAAA;AAEtD,UAAI6B,SAAS,OAAOA,SAAS,EAAKR,UAAS;eAClCQ,SAAS,OAAOA,SAAS,EAAKR,UAAS;AAEhD,UAAIQ,QAAQ,KAAKzB,KAAK,GAAIiB,UAAS;IACrC;AAGA,QAAID,EAAEK,UAAUO,OAAO;AACrB,UAAI5B,KAAK,KAAKgB,EAAEK,SAASO,MAAMC,SAAS,KAAA,EAAQZ,UAAS;AACzD,UAAIjB,MAAM,MAAMgB,EAAEK,SAASO,MAAMC,SAAS,KAAA,EAAQZ,UAAS;IAC7D;AAEA,WAAO;MAAE,GAAGD;MAAGC;MAAOX;IAAW;EACnC,CAAA;AAGAQ,SAAOgB,KAAK,CAACC,GAAGC,MAAMA,EAAEf,QAAQc,EAAEd,KAAK;AACvC,QAAMgB,OAAOnB,OAAO,CAAA;AAEpB,QAAMoB,YAAoC;IAAEC,MAAM;IAAQC,QAAQ;IAAOC,KAAK;EAAM;AAEpF,SAAO;IACLC,YAAYL,KAAKK;IACjBnB,SAASc,KAAKd;IACdC,WAAWa,KAAKb;IAChBmB,OAAON,KAAKM;IACZlB,UAAUY,KAAKZ;IACfmB,QAAQ,GAAGP,KAAK3B,UAAU,mBAAc2B,KAAKd,OAAO,IAAIc,KAAKb,SAAS,YAAYa,KAAKhB,KAAK;EAC9F;AACF;AA9FgB1B;AAqGT,SAASkD,oBACdhD,aACAiD,WAAkB;AAElB,MAAIA,UAAW,QAAO;AACtB,MAAIjD,YAAYe,WAAY,QAAO;AACnC,OAAKf,YAAYU,cAAc,KAAK,KAAM,QAAO;AACjD,SAAO;AACT;AARgBsC;","names":["BackendClient","trpc","serverUrl","token","wsClient","config","replace","isBrowser","window","useWs","useWebSocket","headers","h","wsUrl","createWSClient","url","connectionParams","retryDelayMs","createTRPCClient","links","wsLink","client","transformer","superjson","httpLink","setToken","close","login","username","password","auth","mutate","getMe","me","query","logout","getSystemInfo","system","info","getFeatureFlags","featureFlags","listProviders","providers","list","getProvider","providerId","get","id","startProvider","start","stopProvider","stop","listProviderTypes","providerConfig","getAvailableTypes","listDevices","devices","getDevice","deviceId","discoverDevices","adoptDevice","externalId","createDevice","getAddonAssetUrl","addonId","assetPath","listAddons","addons","getAddonConfigSchema","getConfigSchema","getAddonConfig","getConfig","updateAddonConfig","updateConfig","getAddonLogs","options","getLogs","listKnownFaces","knownFaces","registerFace","label","cropBase64","group","register","listPipelines","bridgePipeline","getPipelineStatus","getPipeline","listAgents","agents","dispatchTask","agentId","task","capability","input","getProcessTree","agent","processTree","bridgeListAddons","bridgeGetPipeline","bridgeSetPipeline","setPipeline","bridgeValidatePipeline","validatePipeline","bridgeGetAddonConfig","bridgeSetAddonConfig","setAddonConfig","bridgeListPackages","bridgeAddons","listPackages","bridgeListAddonsPackages","bridgeInstallPackage","packageName","version","installPackage","bridgeUninstallPackage","uninstallPackage","getRecordingConfig","recording","getRecordingPolicy","getPolicy","getRecordingPolicyStatus","getPolicyStatus","getRecordingSegments","streamId","startTime","endTime","getSegments","getEvents","events","logs","replEval","code","scope","repl","execute","type","listUsers","users","createUser","role","create","getTrackingSessions","session","getActiveTracks","listProcesses","processes","enableProvider","disableProvider","getSettingsSchema","section","settings","getSchema","getSettings","updateSettings","data","update","createBackendClient","DetectionClass","animalClasses","personClasses","vehicleClasses","faceClasses","licensePlateClasses","motionClasses","packageClasses","audioClasses","audioLabelClasses","doorbellClasses","sensorLabelClasses","detectionClassesDefaultMap","reduce","tot","curr","isFaceClassname","c","includes","isPlateClassname","isAnimalClassname","isPersonClassname","isVehicleClassname","isMotionClassname","isDoorbellClassname","isPackageClassname","isAudioClassname","isSensorLabelClassname","isLabelDetection","getParentClass","className","getParentDetectionClass","det","baseMap","parentGroup","defaultDetectionClasses","Object","values","DEFAULT_ENABLED_CLASSES","filter","TIMELINE_PRESET_CRITICAL","TIMELINE_PRESET_IMPORTANT","TIMELINE_PRESET_ALL","getClassesForTimelinePreset","preset","customClasses","length","RAW_TO_CANONICAL","Light","Switch","WindowCovering","Lock","SecuritySystem","Buttons","Select","Siren","Sensor","Entry","Program","MediaPlayer","Outlet","light","switch","input_boolean","cover","lock","alarm_control_panel","input_button","button","input_select","select","siren","sensor","media_player","script","HA_DOMAIN_TYPE_MAP","binary_sensor","climate","camera","fan","vacuum","automation","scene","input_number","person","device_tracker","weather","water_heater","SCRYPTED_TYPE_TO_CANONICAL","Camera","Doorbell","Fan","getCanonicalDeviceType","rawType","canonical","lower","toLowerCase","ELIGIBLE_SCRYPTED_DEVICE_TYPES","ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET","Set","ELIGIBLE_HA_DOMAINS","ELIGIBLE_HA_DOMAINS_SET","FEATURE_MATRIX","id","label","sources","frigate","scrypted","rtsp","adapterMethod","requiresBackend","isFeatureAvailable","featureId","source","platform","entry","find","f","platforms","getSourceFeatures","filter","getBackendRequiredFeatures","selectOptimalStream","streams","constraints","defaultTransport","length","viewportPixels","viewportWidth","viewportHeight","pixelRatio","bw","bandwidthMbps","loss","packetLoss","rtt","rttMs","targetTier","maxResolution","isCellular","PROFILE_TIER","main","sub","ext","preferTransport","scored","map","s","score","streamTier","profile","transport","metadata","width","height","streamPixels","ratio","Math","max","codec","includes","sort","a","b","best","tierNames","high","medium","low","streamName","label","reason","getNextEvalInterval","wasSwitch"]}
|