@roboflow/inference-sdk 0.1.8 → 0.1.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","sources":["../src/inference-api.ts","../src/streams.ts","../src/video-upload.ts","../src/webrtc.ts"],"sourcesContent":["/**\n * Base URL for the Roboflow API (used for TURN server configuration)\n * Can be overridden via environment variable in Node.js environments\n */\nconst RF_API_BASE_URL = typeof process !== \"undefined\" && process.env?.RF_API_BASE_URL\n ? process.env.RF_API_BASE_URL\n : \"https://api.roboflow.com\";\n\n/**\n * List of known Roboflow serverless API URLs where auto TURN config applies\n */\nconst ROBOFLOW_SERVERLESS_URLS = [\n \"https://serverless.roboflow.com\"\n];\n\nexport interface WebRTCWorkerConfig {\n imageInputName?: string;\n streamOutputNames?: string[];\n dataOutputNames?: string[];\n threadPoolWorkers?: number;\n /**\n * Workflow parameters to pass to the workflow execution\n */\n workflowsParameters?: Record<string, any>;\n /**\n * ICE servers for WebRTC connections (used for both client and server)\n */\n iceServers?: RTCIceServerConfig[];\n /**\n * Processing timeout in seconds (serverless only)\n * @default 600\n */\n processingTimeout?: number;\n /**\n * Requested compute plan (serverless only)\n * @example \"webrtc-gpu-small\"\n */\n requestedPlan?: string;\n /**\n * Requested region for processing (serverless only)\n * @example \"us\"\n */\n requestedRegion?: string;\n /**\n * Set to false for file upload mode (batch processing).\n * When false, server processes all frames sequentially instead of dropping frames.\n * @default true\n */\n realtimeProcessing?: boolean;\n}\n\n/**\n * ICE server configuration for WebRTC connections\n *\n * Use this to configure custom STUN/TURN servers for users behind\n * symmetric NAT or restrictive firewalls.\n */\nexport interface RTCIceServerConfig {\n urls: string[];\n username?: string;\n credential?: string;\n}\n\nexport interface WebRTCOffer {\n sdp: string;\n type: string;\n}\n\nexport type WorkflowSpec = Record<string, any>;\n\nexport interface WebRTCWorkerResponse {\n status?: string;\n sdp: string;\n type: string;\n context?: {\n request_id: string | null;\n pipeline_id: string | null;\n };\n}\n\nexport interface WebRTCParams {\n workflowSpec?: WorkflowSpec;\n workspaceName?: string;\n workflowId?: string;\n imageInputName?: string;\n streamOutputNames?: string[];\n dataOutputNames?: string[];\n threadPoolWorkers?: number;\n /**\n * Workflow parameters to pass to the workflow execution\n */\n workflowsParameters?: Record<string, any>;\n /**\n * ICE servers for WebRTC connections (used for both client and server)\n *\n * Use this to specify custom STUN/TURN servers for users behind\n * symmetric NAT or restrictive firewalls. The same configuration is\n * used for both the client-side RTCPeerConnection and sent to the\n * server via webrtc_config.\n *\n * @example\n * ```typescript\n * iceServers: [\n * { urls: [\"stun:stun.l.google.com:19302\"] },\n * { urls: [\"turn:turn.example.com:3478\"], username: \"user\", credential: \"pass\" }\n * ]\n * ```\n */\n iceServers?: RTCIceServerConfig[];\n /**\n * Processing timeout in seconds (serverless only)\n * @default 600\n */\n processingTimeout?: number;\n /**\n * Requested compute plan (serverless only)\n * @example \"webrtc-gpu-small\"\n */\n requestedPlan?: string;\n /**\n * Requested region for processing (serverless only)\n * @example \"us\"\n */\n requestedRegion?: string;\n /**\n * Set to false for file upload mode (batch processing).\n * When false, server processes all frames sequentially instead of dropping frames.\n * @default true\n */\n realtimeProcessing?: boolean;\n}\n\nexport interface Connector {\n connectWrtc(offer: WebRTCOffer, wrtcParams: WebRTCParams): Promise<WebRTCWorkerResponse>;\n /**\n * Fetch ICE servers (TURN configuration) for WebRTC connections\n * This should be called BEFORE creating the RTCPeerConnection to ensure\n * proper NAT traversal configuration.\n *\n * @returns Promise resolving to ICE server configuration, or null/undefined if not available\n */\n getIceServers?(): Promise<RTCIceServerConfig[] | null>;\n _apiKey?: string;\n _serverUrl?: string;\n}\n\nexport class InferenceHTTPClient {\n private apiKey: string;\n private serverUrl: string;\n\n /**\n * @private\n * Use InferenceHTTPClient.init() instead\n */\n private constructor(apiKey: string, serverUrl: string = \"https://serverless.roboflow.com\") {\n this.apiKey = apiKey;\n this.serverUrl = serverUrl;\n }\n\n static init({ apiKey, serverUrl }: { apiKey: string; serverUrl?: string }): InferenceHTTPClient {\n if (!apiKey) {\n throw new Error(\"apiKey is required\");\n }\n return new InferenceHTTPClient(apiKey, serverUrl);\n }\n\n /**\n * Initialize a WebRTC worker pipeline\n *\n * @param params - Pipeline parameters\n * @param params.offer - WebRTC offer { sdp, type }\n * @param params.workflowSpec - Workflow specification\n * @param params.config - Additional configuration\n * @param params.config.imageInputName - Input image name (default: \"image\")\n * @param params.config.streamOutputNames - Output stream names for video (default: [])\n * @param params.config.dataOutputNames - Output data names (default: [\"string\"])\n * @param params.config.threadPoolWorkers - Thread pool workers (default: 4)\n * @returns Promise resolving to answer with SDP and pipeline ID\n *\n * @example\n * ```typescript\n * const answer = await client.initializeWebrtcWorker({\n * offer: { sdp, type },\n * workflowSpec: { ... },\n * config: {\n * imageInputName: \"image\",\n * streamOutputNames: [\"output_image\"]\n * }\n * });\n * ```\n */\n async initializeWebrtcWorker({\n offer,\n workflowSpec,\n workspaceName,\n workflowId,\n config = {}\n }: {\n offer: WebRTCOffer;\n workflowSpec?: WorkflowSpec;\n workspaceName?: string;\n workflowId?: string;\n config?: WebRTCWorkerConfig;\n }): Promise<WebRTCWorkerResponse> {\n if (!offer || !offer.sdp || !offer.type) {\n throw new Error(\"offer with sdp and type is required\");\n }\n\n // Validate that either workflowSpec OR (workspaceName + workflowId) is provided\n const hasWorkflowSpec = !!workflowSpec;\n const hasWorkspaceIdentifier = !!(workspaceName && workflowId);\n\n if (!hasWorkflowSpec && !hasWorkspaceIdentifier) {\n throw new Error(\"Either workflowSpec OR (workspaceName + workflowId) is required\");\n }\n if (hasWorkflowSpec && hasWorkspaceIdentifier) {\n throw new Error(\"Provide either workflowSpec OR (workspaceName + workflowId), not both\");\n }\n\n const {\n imageInputName = \"image\",\n streamOutputNames = [],\n dataOutputNames = [\"string\"],\n threadPoolWorkers = 4,\n workflowsParameters = {},\n iceServers,\n processingTimeout,\n requestedPlan,\n requestedRegion,\n realtimeProcessing = true\n } = config as any;\n\n // Build workflow_configuration based on what's provided\n const workflowConfiguration: any = {\n type: \"WorkflowConfiguration\",\n image_input_name: imageInputName,\n workflows_parameters: workflowsParameters,\n workflows_thread_pool_workers: threadPoolWorkers,\n cancel_thread_pool_tasks_on_exit: true,\n video_metadata_input_name: \"video_metadata\"\n };\n\n if (hasWorkflowSpec) {\n workflowConfiguration.workflow_specification = workflowSpec;\n } else {\n workflowConfiguration.workspace_name = workspaceName;\n workflowConfiguration.workflow_id = workflowId;\n }\n\n const payload: Record<string, any> = {\n workflow_configuration: workflowConfiguration,\n api_key: this.apiKey,\n webrtc_realtime_processing: realtimeProcessing,\n webrtc_offer: {\n sdp: offer.sdp,\n type: offer.type\n },\n webrtc_config: iceServers ? { iceServers } : null,\n stream_output: streamOutputNames,\n data_output: dataOutputNames\n };\n\n // Add serverless-specific fields if provided\n if (processingTimeout !== undefined) {\n payload.processing_timeout = processingTimeout;\n }\n if (requestedPlan !== undefined) {\n payload.requested_plan = requestedPlan;\n }\n if (requestedRegion !== undefined) {\n payload.requested_region = requestedRegion;\n }\n const response = await fetch(`${this.serverUrl}/initialise_webrtc_worker`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"\");\n throw new Error(`initialise_webrtc_worker failed (${response.status}): ${errorText}`);\n }\n\n const result = await response.json();\n\n return result;\n }\n\n async terminatePipeline({ pipelineId }: { pipelineId: string }): Promise<void> {\n if (!pipelineId) {\n throw new Error(\"pipelineId is required\");\n }\n\n await fetch(\n `${this.serverUrl}/inference_pipelines/${pipelineId}/terminate?api_key=${this.apiKey}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" }\n }\n );\n }\n\n /**\n * Fetch TURN server configuration from Roboflow API\n *\n * This automatically fetches TURN server credentials for improved WebRTC\n * connectivity through firewalls and NAT. Only applicable when using\n * Roboflow serverless infrastructure.\n *\n * @returns Promise resolving to ICE server configuration, or null if not applicable\n *\n * @example\n * ```typescript\n * const client = InferenceHTTPClient.init({ apiKey: \"your-api-key\" });\n * const iceServers = await client.fetchTurnConfig();\n * // Returns: [{ urls: [\"turn:...\"], username: \"...\", credential: \"...\" }]\n * ```\n */\n async fetchTurnConfig(): Promise<RTCIceServerConfig[] | null> {\n // // Only fetch TURN config for Roboflow serverless URLs\n if (!ROBOFLOW_SERVERLESS_URLS.includes(this.serverUrl)) {\n return null;\n }\n try {\n const response = await fetch(\n `${RF_API_BASE_URL}/webrtc_turn_config?api_key=${this.apiKey}`,\n {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" }\n }\n );\n\n if (!response.ok) {\n console.warn(`[RFWebRTC] Failed to fetch TURN config (${response.status}), using defaults`);\n return null;\n }\n\n const turnConfig = await response.json();\n\n // Handle 3 formats:\n // 1. Single server object: { urls, username, credential }\n // 2. Array of servers: [{ urls, username, credential }, ...]\n // 3. Object with iceServers: { iceServers: [...] }\n let iceServersRaw: any[];\n\n if (Array.isArray(turnConfig)) {\n // Format 2: array of servers\n iceServersRaw = turnConfig;\n } else if (turnConfig.iceServers && Array.isArray(turnConfig.iceServers)) {\n // Format 3: object with iceServers array\n iceServersRaw = turnConfig.iceServers;\n } else if (turnConfig.urls) {\n // Format 1: single server object - wrap in array\n iceServersRaw = [turnConfig];\n } else {\n console.warn(\"[RFWebRTC] Invalid TURN config format, using defaults\");\n return null;\n }\n\n // Normalize the ICE servers format\n const iceServers: RTCIceServerConfig[] = iceServersRaw.map((server: any) => ({\n urls: Array.isArray(server.urls) ? server.urls : [server.urls],\n username: server.username,\n credential: server.credential\n }));\n\n return iceServers;\n } catch (err) {\n console.warn(\"[RFWebRTC] Error fetching TURN config:\", err);\n return null;\n }\n }\n}\n\n/**\n * Connectors for establishing WebRTC connections to Roboflow\n */\nexport const connectors = {\n /**\n * Create a connector that uses API key directly\n *\n * **WARNING**: If you use this in the frontend, it will expose your API key. \n * Use only for demos/testing.\n * For production, use withProxyUrl() with a backend proxy.\n *\n * @param apiKey - Roboflow API key\n * @param options - Additional options\n * @param options.serverUrl - Custom Roboflow server URL\n * @returns Connector with connectWrtc method\n *\n * @example\n * ```typescript\n * const connector = connectors.withApiKey(\"your-api-key\");\n * const answer = await connector.connectWrtc(offer, wrtcParams);\n * ```\n */\n withApiKey(apiKey: string, options: { serverUrl?: string } = {}): Connector {\n const { serverUrl } = options;\n\n // Warn if running in browser context\n if (typeof window !== 'undefined') {\n console.warn(\n '[Security Warning] Using API key directly in browser will expose it. ' +\n 'Use connectors.withProxyUrl() for production. ' +\n 'See: https://docs.roboflow.com/api-reference/authentication#securing-your-api-key'\n );\n }\n\n const client = InferenceHTTPClient.init({ apiKey, serverUrl });\n\n return {\n connectWrtc: async (offer: WebRTCOffer, wrtcParams: WebRTCParams): Promise<WebRTCWorkerResponse> => {\n console.debug(\"wrtcParams\", wrtcParams);\n const answer = await client.initializeWebrtcWorker({\n offer,\n workflowSpec: wrtcParams.workflowSpec,\n workspaceName: wrtcParams.workspaceName,\n workflowId: wrtcParams.workflowId,\n config: {\n imageInputName: wrtcParams.imageInputName,\n streamOutputNames: wrtcParams.streamOutputNames,\n dataOutputNames: wrtcParams.dataOutputNames,\n threadPoolWorkers: wrtcParams.threadPoolWorkers,\n workflowsParameters: wrtcParams.workflowsParameters,\n iceServers: wrtcParams.iceServers,\n processingTimeout: wrtcParams.processingTimeout,\n requestedPlan: wrtcParams.requestedPlan,\n requestedRegion: wrtcParams.requestedRegion,\n realtimeProcessing: wrtcParams.realtimeProcessing\n }\n });\n\n return answer;\n },\n\n /**\n * Fetch TURN server configuration for improved WebRTC connectivity\n */\n getIceServers: async (): Promise<RTCIceServerConfig[] | null> => {\n return await client.fetchTurnConfig();\n },\n\n // Store apiKey for cleanup\n _apiKey: apiKey,\n _serverUrl: serverUrl\n };\n },\n\n /**\n * Create a connector that uses a backend proxy (recommended for production)\n *\n * Your backend receives the offer and wrtcParams, adds the secret API key,\n * and forwards to Roboflow. This keeps your API key secure.\n *\n * For improved WebRTC connectivity through firewalls, implement a separate\n * endpoint for TURN server configuration that calls `fetchTurnConfig()`.\n *\n * @param proxyUrl - Backend proxy endpoint URL for WebRTC initialization\n * @param options - Additional options\n * @param options.turnConfigUrl - Optional URL for fetching TURN server configuration\n * @returns Connector with connectWrtc and optional getIceServers methods\n *\n * @example\n * ```typescript\n * // Frontend: Create connector with TURN config endpoint\n * const connector = connectors.withProxyUrl('/api/init-webrtc', {\n * turnConfigUrl: '/api/turn-config'\n * });\n * ```\n *\n * @example\n * Backend implementation (Express) with TURN server support:\n * ```typescript\n * // Endpoint for TURN configuration (called first by SDK)\n * app.get('/api/turn-config', async (req, res) => {\n * const client = InferenceHTTPClient.init({\n * apiKey: process.env.ROBOFLOW_API_KEY\n * });\n * const iceServers = await client.fetchTurnConfig();\n * res.json({ iceServers });\n * });\n *\n * // Endpoint for WebRTC initialization\n * app.post('/api/init-webrtc', async (req, res) => {\n * const { offer, wrtcParams } = req.body;\n * const client = InferenceHTTPClient.init({\n * apiKey: process.env.ROBOFLOW_API_KEY\n * });\n *\n * const answer = await client.initializeWebrtcWorker({\n * offer,\n * workflowSpec: wrtcParams.workflowSpec,\n * workspaceName: wrtcParams.workspaceName,\n * workflowId: wrtcParams.workflowId,\n * config: {\n * imageInputName: wrtcParams.imageInputName,\n * streamOutputNames: wrtcParams.streamOutputNames,\n * dataOutputNames: wrtcParams.dataOutputNames,\n * threadPoolWorkers: wrtcParams.threadPoolWorkers,\n * workflowsParameters: wrtcParams.workflowsParameters,\n * iceServers: wrtcParams.iceServers,\n * processingTimeout: wrtcParams.processingTimeout,\n * requestedPlan: wrtcParams.requestedPlan,\n * requestedRegion: wrtcParams.requestedRegion\n * }\n * });\n *\n * res.json(answer);\n * });\n * ```\n */\n withProxyUrl(proxyUrl: string, options: { turnConfigUrl?: string } = {}): Connector {\n const { turnConfigUrl } = options;\n\n return {\n connectWrtc: async (offer: WebRTCOffer, wrtcParams: WebRTCParams): Promise<WebRTCWorkerResponse> => {\n const response = await fetch(proxyUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n offer,\n wrtcParams\n })\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"\");\n throw new Error(`Proxy request failed (${response.status}): ${errorText}`);\n }\n\n return await response.json();\n },\n\n /**\n * Fetch TURN server configuration from the proxy backend\n * Only available if turnConfigUrl was provided\n */\n getIceServers: turnConfigUrl\n ? async (): Promise<RTCIceServerConfig[] | null> => {\n try {\n const response = await fetch(turnConfigUrl, {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" }\n });\n\n if (!response.ok) {\n console.warn(`[RFWebRTC] Failed to fetch TURN config from proxy (${response.status})`);\n return null;\n }\n\n const data = await response.json();\n return data.iceServers || null;\n } catch (err) {\n console.warn(\"[RFWebRTC] Error fetching TURN config from proxy:\", err);\n return null;\n }\n }\n : undefined\n };\n }\n};\n","/**\n * Get a camera stream with the given constraints.\n *\n * @param constraints - MediaStreamConstraints for getUserMedia\n * @returns Promise that resolves to MediaStream\n *\n * @example\n * ```typescript\n * const stream = await useCamera({\n * video: {\n * facingMode: { ideal: \"user\" },\n * width: { ideal: 1280 },\n * height: { ideal: 720 },\n * frameRate: { ideal: 30 }\n * },\n * audio: false\n * });\n * ```\n */\nexport async function useCamera(constraints: MediaStreamConstraints = { video: true }): Promise<MediaStream> {\n try {\n console.log(\"[RFStreams] requesting with\", constraints);\n const stream = await navigator.mediaDevices.getUserMedia(constraints);\n console.log(\"[RFStreams] got stream\", stream.getVideoTracks().map(t => ({ id: t.id, label: t.label })));\n return stream;\n } catch (err) {\n console.warn(\"[RFStreams] failed, falling back\", err);\n const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });\n console.log(\"[RFStreams] fallback stream\", stream.getVideoTracks().map(t => ({ id: t.id, label: t.label })));\n return stream;\n }\n}\n \nexport function stopStream(stream: MediaStream | null | undefined): void {\n if (stream) {\n stream.getTracks().forEach(track => track.stop());\n console.log(\"[RFStreams] Stream stopped\");\n }\n}\n","/**\n * Video file upload via WebRTC datachannel\n *\n * This module provides the FileUploader class for chunked file uploads\n * through WebRTC datachannels with backpressure handling.\n */\n\n/**\n * Configuration constants for file upload (matching Python SDK)\n */\nconst CHUNK_SIZE = 49152; // 49KB - safe for WebRTC\nconst BUFFER_LIMIT = 262144; // 256KB - backpressure threshold\nconst POLL_INTERVAL = 10; // 10ms buffer check interval\n\n/**\n * Helper to sleep for a given number of milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * FileUploader handles chunked file upload with backpressure\n *\n * Uploads files through a WebRTC datachannel in 49KB chunks with\n * intelligent backpressure handling to prevent overwhelming the network.\n */\nexport class FileUploader {\n private file: File;\n private channel: RTCDataChannel;\n private totalChunks: number;\n private cancelled: boolean = false;\n\n constructor(file: File, channel: RTCDataChannel) {\n this.file = file;\n this.channel = channel;\n this.totalChunks = Math.ceil(file.size / CHUNK_SIZE);\n }\n\n /**\n * Cancel the upload\n */\n cancel(): void {\n this.cancelled = true;\n }\n\n /**\n * Upload the file in chunks with backpressure handling\n *\n * @param onProgress - Optional callback for progress updates (bytesUploaded, totalBytes)\n */\n async upload(onProgress?: (bytesUploaded: number, totalBytes: number) => void): Promise<void> {\n const totalBytes = this.file.size;\n\n for (let chunkIndex = 0; chunkIndex < this.totalChunks; chunkIndex++) {\n // Check for cancellation\n if (this.cancelled) {\n throw new Error(\"Upload cancelled\");\n }\n\n // Check channel state\n if (this.channel.readyState !== \"open\") {\n throw new Error(\"Video upload interrupted\");\n }\n\n // Read chunk from file\n const start = chunkIndex * CHUNK_SIZE;\n const end = Math.min(start + CHUNK_SIZE, totalBytes);\n const chunkBlob = this.file.slice(start, end);\n const chunkData = new Uint8Array(await chunkBlob.arrayBuffer());\n\n // Create message with 8-byte header (chunkIndex + totalChunks as uint32 LE)\n const message = new ArrayBuffer(8 + chunkData.length);\n const view = new DataView(message);\n view.setUint32(0, chunkIndex, true); // little-endian\n view.setUint32(4, this.totalChunks, true); // little-endian\n new Uint8Array(message, 8).set(chunkData);\n\n // Backpressure: wait for buffer to drain\n while (this.channel.bufferedAmount > BUFFER_LIMIT) {\n if (this.channel.readyState !== \"open\") {\n throw new Error(\"Video upload interrupted\");\n }\n await sleep(POLL_INTERVAL);\n }\n\n // Send chunk\n this.channel.send(message);\n\n // Report progress\n if (onProgress) {\n onProgress(end, totalBytes);\n }\n }\n }\n}\n","\nimport { InferenceHTTPClient, Connector, WebRTCParams, RTCIceServerConfig } from \"./inference-api\";\nimport { stopStream } from \"./streams\";\nimport { WebRTCOutputData } from \"./webrtc-types\";\nimport { FileUploader } from \"./video-upload\";\n\n// Re-export shared types\nexport type { WebRTCVideoMetadata, WebRTCOutputData } from \"./webrtc-types\";\n\n// Re-export FileUploader from video-upload\nexport { FileUploader } from \"./video-upload\";\n\n/**\n * Binary protocol header size (frame_id + chunk_index + total_chunks)\n * Each field is 4 bytes uint32 little-endian\n */\nconst HEADER_SIZE = 12;\n\n/**\n * Reassembles chunked binary messages from the datachannel\n */\nexport class ChunkReassembler {\n private pendingFrames: Map<number, {\n chunks: Map<number, Uint8Array>;\n totalChunks: number;\n }> = new Map();\n\n /**\n * Process an incoming chunk and return the complete message if all chunks received\n */\n processChunk(frameId: number, chunkIndex: number, totalChunks: number, payload: Uint8Array): Uint8Array | null {\n // Single chunk message - return immediately\n if (totalChunks === 1) {\n return payload;\n }\n\n // Multi-chunk message - accumulate\n if (!this.pendingFrames.has(frameId)) {\n this.pendingFrames.set(frameId, {\n chunks: new Map(),\n totalChunks\n });\n }\n\n const frame = this.pendingFrames.get(frameId)!;\n frame.chunks.set(chunkIndex, payload);\n\n // Check if all chunks received\n if (frame.chunks.size === totalChunks) {\n // Reassemble in order\n const totalLength = Array.from(frame.chunks.values()).reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n\n for (let i = 0; i < totalChunks; i++) {\n const chunk = frame.chunks.get(i)!;\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n this.pendingFrames.delete(frameId);\n return result;\n }\n\n return null;\n }\n\n /**\n * Clear all pending frames (for cleanup)\n */\n clear(): void {\n this.pendingFrames.clear();\n }\n}\n\n/**\n * Parse the binary header from a datachannel message\n */\nexport function parseBinaryHeader(buffer: ArrayBuffer): { frameId: number; chunkIndex: number; totalChunks: number; payload: Uint8Array } {\n const view = new DataView(buffer);\n const frameId = view.getUint32(0, true); // little-endian\n const chunkIndex = view.getUint32(4, true); // little-endian\n const totalChunks = view.getUint32(8, true); // little-endian\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n\n return { frameId, chunkIndex, totalChunks, payload };\n}\n\nexport interface UseStreamOptions {\n disableInputStreamDownscaling?: boolean;\n}\n\nexport interface UseStreamParams {\n source: MediaStream;\n connector: Connector;\n wrtcParams: WebRTCParams;\n onData?: (data: WebRTCOutputData) => void;\n options?: UseStreamOptions;\n}\n\nasync function waitForIceGathering(pc: RTCPeerConnection, timeoutMs = 6000): Promise<void> {\n if (pc.iceGatheringState === \"complete\") return;\n\n let hasSrflx = false;\n\n // Track if we get a good candidate (srflx = public IP via STUN)\n const candidateHandler = (event: RTCPeerConnectionIceEvent) => {\n if (event.candidate && event.candidate.type === \"srflx\") {\n hasSrflx = true;\n }\n };\n pc.addEventListener(\"icecandidate\", candidateHandler);\n\n try {\n await Promise.race([\n new Promise<void>(resolve => {\n const check = () => {\n if (pc.iceGatheringState === \"complete\") {\n pc.removeEventListener(\"icegatheringstatechange\", check);\n resolve();\n }\n };\n pc.addEventListener(\"icegatheringstatechange\", check);\n }),\n new Promise<void>((resolve, reject) => {\n setTimeout(() => {\n if (!hasSrflx) {\n console.error(\"[ICE] timeout with NO srflx candidate! Connection may fail.\");\n reject(new Error(\"ICE gathering timeout without srflx candidate\"));\n } else {\n resolve();\n }\n }, timeoutMs);\n })\n ]);\n } finally {\n pc.removeEventListener(\"icecandidate\", candidateHandler);\n }\n}\n\nfunction setupRemoteStreamListener(pc: RTCPeerConnection): Promise<MediaStream> {\n return new Promise((resolve) => {\n pc.addEventListener(\"track\", (event: RTCTrackEvent) => {\n if (event.streams && event.streams[0]) {\n resolve(event.streams[0]);\n }\n });\n });\n}\n\nconst DEFAULT_ICE_SERVERS: RTCIceServerConfig[] = [\n { urls: [\"stun:stun.l.google.com:19302\"] }\n];\n\nasync function preparePeerConnection(\n localStream?: MediaStream,\n file?: File,\n customIceServers?: RTCIceServerConfig[]\n): Promise<{\n pc: RTCPeerConnection;\n offer: RTCSessionDescriptionInit;\n remoteStreamPromise: Promise<MediaStream>;\n dataChannel: RTCDataChannel;\n uploadChannel?: RTCDataChannel;\n}> {\n if (!localStream && !file || (localStream && file)) {\n throw new Error(\"Either localStream or file must be provided, but not both\");\n }\n const iceServers = customIceServers ?? DEFAULT_ICE_SERVERS;\n\n const pc = new RTCPeerConnection({\n iceServers: iceServers as RTCIceServer[]\n });\n\n // Add transceiver for receiving remote video (BEFORE adding tracks - order matters!)\n try {\n pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n } catch (err) {\n console.warn(\"[RFWebRTC] Could not add transceiver:\", err);\n }\n\n if (localStream) {\n // Add local tracks\n localStream.getVideoTracks().forEach(track => {\n try {\n // @ts-ignore - contentHint is not in all TypeScript definitions\n track.contentHint = \"detail\";\n } catch (e) {\n // Ignore if contentHint not supported\n }\n pc.addTrack(track, localStream);\n });\n }\n\n // Setup remote stream listener\n const remoteStreamPromise = setupRemoteStreamListener(pc);\n\n // Create control datachannel (named \"inference\" to match Python SDK)\n const dataChannel = pc.createDataChannel(\"inference\", {\n ordered: true\n });\n\n // Create upload datachannel for file uploads\n let uploadChannel: RTCDataChannel | undefined;\n if (file) {\n uploadChannel = pc.createDataChannel(\"video_upload\");\n }\n\n // Create offer\n const offer = await pc.createOffer();\n await pc.setLocalDescription(offer);\n\n // Wait for ICE gathering\n await waitForIceGathering(pc);\n\n return {\n pc,\n offer: pc.localDescription!,\n remoteStreamPromise,\n dataChannel,\n uploadChannel\n };\n}\n\n/**\n * Disable input stream downscaling\n * @private\n */\nasync function disableInputStreamDownscaling(pc: RTCPeerConnection): Promise<void> {\n const sender = pc.getSenders().find(s => s.track && s.track.kind === \"video\");\n if (!sender) return;\n\n const params = sender.getParameters();\n params.encodings = params.encodings || [{}];\n params.encodings[0].scaleResolutionDownBy = 1;\n\n try {\n await sender.setParameters(params);\n } catch (err) {\n console.warn(\"[RFWebRTC] Failed to set encoding parameters:\", err);\n }\n}\n\n/**\n * Helper to wait for datachannel to open\n */\nfunction waitForChannelOpen(channel: RTCDataChannel, timeoutMs = 30000): Promise<void> {\n return new Promise((resolve, reject) => {\n if (channel.readyState === \"open\") {\n resolve();\n return;\n }\n\n const openHandler = () => {\n channel.removeEventListener(\"open\", openHandler);\n channel.removeEventListener(\"error\", errorHandler);\n clearTimeout(timeout);\n resolve();\n };\n\n const errorHandler = () => {\n channel.removeEventListener(\"open\", openHandler);\n channel.removeEventListener(\"error\", errorHandler);\n clearTimeout(timeout);\n reject(new Error(\"Datachannel error\"));\n };\n\n const timeout = setTimeout(() => {\n channel.removeEventListener(\"open\", openHandler);\n channel.removeEventListener(\"error\", errorHandler);\n reject(new Error(\"Datachannel open timeout\"));\n }, timeoutMs);\n\n channel.addEventListener(\"open\", openHandler);\n channel.addEventListener(\"error\", errorHandler);\n });\n}\n\n/**\n * WebRTC Connection object\n *\n * Represents an active WebRTC connection to Roboflow for streaming inference\n * or file-based batch processing.\n */\nexport class RFWebRTCConnection {\n private pc: RTCPeerConnection;\n private _localStream?: MediaStream;\n private remoteStreamPromise: Promise<MediaStream>;\n private pipelineId: string | null;\n private apiKey: string | null;\n private dataChannel: RTCDataChannel;\n private reassembler: ChunkReassembler;\n private uploadChannel?: RTCDataChannel;\n private uploader?: FileUploader;\n private onComplete?: () => void;\n\n /** @private */\n constructor(\n pc: RTCPeerConnection,\n remoteStreamPromise: Promise<MediaStream>,\n pipelineId: string | null,\n apiKey: string | null,\n dataChannel: RTCDataChannel,\n options?: {\n localStream?: MediaStream;\n uploadChannel?: RTCDataChannel;\n onData?: (data: any) => void;\n onComplete?: () => void;\n }\n ) {\n this.pc = pc;\n this._localStream = options?.localStream;\n this.remoteStreamPromise = remoteStreamPromise;\n this.pipelineId = pipelineId;\n this.apiKey = apiKey;\n this.dataChannel = dataChannel;\n this.reassembler = new ChunkReassembler();\n this.uploadChannel = options?.uploadChannel;\n this.onComplete = options?.onComplete;\n\n // Set binary mode for datachannel\n this.dataChannel.binaryType = \"arraybuffer\";\n\n const onData = options?.onData;\n\n // Setup data channel event listeners\n if (onData) {\n this.dataChannel.addEventListener(\"message\", (messageEvent: MessageEvent) => {\n try {\n // Handle binary protocol with chunking\n if (messageEvent.data instanceof ArrayBuffer) {\n const { frameId, chunkIndex, totalChunks, payload } = parseBinaryHeader(messageEvent.data);\n const completePayload = this.reassembler.processChunk(frameId, chunkIndex, totalChunks, payload);\n\n if (completePayload) {\n // Decode UTF-8 JSON payload\n const decoder = new TextDecoder(\"utf-8\");\n const jsonString = decoder.decode(completePayload);\n const data = JSON.parse(jsonString);\n onData(data);\n }\n } else {\n // Fallback for string messages (shouldn't happen with new protocol)\n const data = JSON.parse(messageEvent.data);\n onData(data);\n }\n } catch (err) {\n console.error(\"[RFWebRTC] Failed to parse data channel message:\", err);\n }\n });\n\n this.dataChannel.addEventListener(\"error\", (error) => {\n console.error(\"[RFWebRTC] Data channel error:\", error);\n });\n }\n\n // Handle channel close - call onComplete when processing finishes\n this.dataChannel.addEventListener(\"close\", () => {\n this.reassembler.clear();\n if (this.onComplete) {\n this.onComplete();\n }\n });\n }\n\n /**\n * Get the remote stream (processed video from Roboflow)\n *\n * @returns Promise resolving to the remote MediaStream\n *\n * @example\n * ```typescript\n * const conn = await useStream({ ... });\n * const remoteStream = await conn.remoteStream();\n * videoElement.srcObject = remoteStream;\n * ```\n */\n async remoteStream(): Promise<MediaStream> {\n return await this.remoteStreamPromise;\n }\n\n /**\n * Get the local stream (original camera)\n *\n * @returns The local MediaStream, or undefined if using file upload mode\n *\n * @example\n * ```typescript\n * const conn = await useStream({ ... });\n * const localStream = conn.localStream();\n * if (localStream) {\n * videoElement.srcObject = localStream;\n * }\n * ```\n */\n localStream(): MediaStream | undefined {\n return this._localStream;\n }\n\n /**\n * Cleanup and close connection\n *\n * Terminates the pipeline on Roboflow, closes the peer connection,\n * and stops the local media stream (if applicable).\n *\n * @returns Promise that resolves when cleanup is complete\n *\n * @example\n * ```typescript\n * const conn = await useStream({ ... });\n * // ... use connection ...\n * await conn.cleanup(); // Clean up when done\n * ```\n */\n async cleanup(): Promise<void> {\n // Cancel any ongoing upload\n if (this.uploader) {\n this.uploader.cancel();\n }\n\n // Clear pending chunks\n this.reassembler.clear();\n\n // Terminate pipeline\n if (this.pipelineId && this.apiKey) {\n try {\n const client = InferenceHTTPClient.init({ apiKey: this.apiKey });\n await client.terminatePipeline({ pipelineId: this.pipelineId });\n } catch (err) {\n console.warn(\"[RFWebRTC] Failed to terminate pipeline:\", err);\n }\n }\n\n // Close peer connection\n if (this.pc && this.pc.connectionState !== \"closed\") {\n this.pc.close();\n }\n\n // Stop local stream if present\n if (this._localStream) {\n stopStream(this._localStream);\n }\n }\n\n /**\n * Start uploading a file through the connection\n *\n * @param file - The file to upload\n * @param onProgress - Optional callback for progress updates (bytesUploaded, totalBytes)\n * @returns Promise that resolves when upload is complete\n * @throws Error if no upload channel is available\n *\n * @example\n * ```typescript\n * await connection.startUpload(videoFile, (uploaded, total) => {\n * console.log(`Upload progress: ${(uploaded / total * 100).toFixed(1)}%`);\n * });\n * ```\n */\n async startUpload(file: File, onProgress?: (bytesUploaded: number, totalBytes: number) => void): Promise<void> {\n if (!this.uploadChannel) {\n throw new Error(\"No upload channel available. This connection was not created for file uploads.\");\n }\n\n // Wait for upload channel to open\n await waitForChannelOpen(this.uploadChannel);\n\n this.uploader = new FileUploader(file, this.uploadChannel);\n await this.uploader.upload(onProgress);\n }\n\n /**\n * Cancel any ongoing file upload\n */\n cancelUpload(): void {\n if (this.uploader) {\n this.uploader.cancel();\n }\n }\n\n /**\n * Reconfigure pipeline outputs at runtime\n *\n * Dynamically change stream and data outputs without restarting the connection.\n * Set a field to `null` to leave it unchanged, or to `null` value to enable all outputs,\n * or to `[]` to disable/auto-detect.\n *\n * @param config - Output configuration\n * @param config.streamOutput - Stream output names (null = unchanged, [] = auto-detect, [\"name\"] = specific output)\n * @param config.dataOutput - Data output names (null = unchanged, [] = disable, [\"name\"] = specific outputs, null value = all outputs)\n *\n * @example\n * ```typescript\n * // Change to different stream output\n * connection.reconfigureOutputs({\n * streamOutput: [\"annotated_image\"],\n * dataOutput: null // unchanged\n * });\n *\n * // Enable all data outputs\n * connection.reconfigureOutputs({\n * streamOutput: null, // unchanged\n * dataOutput: null // null value = all outputs\n * });\n *\n * // Disable all data outputs\n * connection.reconfigureOutputs({\n * streamOutput: null, // unchanged\n * dataOutput: [] // empty array = disable\n * });\n * ```\n */\n reconfigureOutputs(config: { streamOutput?: string[] | null; dataOutput?: string[] | null }): void {\n const message: any = {};\n\n if (config.streamOutput !== undefined) {\n message.stream_output = config.streamOutput;\n }\n\n if (config.dataOutput !== undefined) {\n message.data_output = config.dataOutput;\n }\n\n this.sendData(message);\n }\n\n /**\n * Send data through the data channel\n * @private\n */\n private sendData(data: any): void {\n if (this.dataChannel.readyState !== \"open\") {\n console.warn(\"[RFWebRTC] Data channel is not open. Current state:\", this.dataChannel.readyState);\n return;\n }\n\n try {\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n this.dataChannel.send(message);\n } catch (err) {\n console.error(\"[RFWebRTC] Failed to send data:\", err);\n }\n }\n}\n\n/**\n * Internal base function for establishing WebRTC connection\n * Used by both useStream and useVideoFile\n * @private\n */\ninterface BaseUseStreamParams {\n source: MediaStream | File;\n connector: Connector;\n wrtcParams: WebRTCParams;\n onData?: (data: WebRTCOutputData) => void;\n onComplete?: () => void;\n onFileUploadProgress?: (bytesUploaded: number, totalBytes: number) => void;\n options?: UseStreamOptions;\n}\n\nasync function baseUseStream({\n source,\n connector,\n wrtcParams,\n onData,\n onComplete,\n onFileUploadProgress,\n options = {}\n}: BaseUseStreamParams): Promise<RFWebRTCConnection> {\n // Validate connector\n if (!connector || typeof connector.connectWrtc !== \"function\") {\n throw new Error(\"connector must have a connectWrtc method\");\n }\n\n const isFile = source instanceof File;\n const localStream = isFile ? undefined : source;\n const file = isFile ? source : undefined;\n\n // Step 1: Determine ICE servers to use\n // Priority: 1) User-provided in wrtcParams, 2) From connector.getIceServers(), 3) Defaults\n let iceServers = wrtcParams.iceServers;\n if ((!iceServers || iceServers.length === 0) && connector.getIceServers) {\n try {\n const turnConfig = await connector.getIceServers();\n if (turnConfig && turnConfig.length > 0) {\n iceServers = turnConfig;\n console.log(\"[RFWebRTC] Using TURN servers from connector\");\n }\n } catch (err) {\n console.warn(\"[RFWebRTC] Failed to fetch TURN config, using defaults:\", err);\n }\n }\n\n // Step 2: Prepare peer connection and create offer\n const { pc, offer, remoteStreamPromise, dataChannel, uploadChannel } = await preparePeerConnection(\n localStream,\n file,\n iceServers\n );\n\n // Update wrtcParams with resolved iceServers so server also uses them\n // For file uploads, default to batch mode (realtimeProcessing: false)\n const resolvedWrtcParams = {\n ...wrtcParams,\n iceServers: iceServers,\n realtimeProcessing: wrtcParams.realtimeProcessing ?? !isFile\n };\n\n // Step 3: Call connector.connectWrtc to exchange SDP and get answer\n const answer = await connector.connectWrtc(\n { sdp: offer.sdp!, type: offer.type! },\n resolvedWrtcParams\n );\n\n // API returns sdp and type at root level\n const sdpAnswer = { sdp: answer.sdp, type: answer.type } as RTCSessionDescriptionInit;\n\n if (!sdpAnswer?.sdp || !sdpAnswer?.type) {\n console.error(\"[RFWebRTC] Invalid answer from server:\", answer);\n throw new Error(\"connector.connectWrtc must return answer with sdp and type\");\n }\n\n const pipelineId = answer?.context?.pipeline_id || null;\n\n // Step 4: Set remote description\n await pc.setRemoteDescription(sdpAnswer);\n\n // Step 5: Wait for connection to establish\n await new Promise<void>((resolve, reject) => {\n const checkState = () => {\n if (pc.connectionState === \"connected\") {\n pc.removeEventListener(\"connectionstatechange\", checkState);\n resolve();\n } else if (pc.connectionState === \"failed\") {\n pc.removeEventListener(\"connectionstatechange\", checkState);\n reject(new Error(\"WebRTC connection failed\"));\n }\n };\n\n pc.addEventListener(\"connectionstatechange\", checkState);\n checkState(); // Check immediately in case already connected\n\n // Timeout after 30 seconds\n setTimeout(() => {\n pc.removeEventListener(\"connectionstatechange\", checkState);\n reject(new Error(\"WebRTC connection timeout after 30s\"));\n }, 30000);\n });\n\n // Step 6: Optimize quality for MediaStream (disable downsampling by default)\n if (localStream) {\n const shouldDisableDownscaling = options.disableInputStreamDownscaling !== false;\n if (shouldDisableDownscaling) {\n await disableInputStreamDownscaling(pc);\n }\n }\n\n // Get apiKey from connector if available (for cleanup)\n const apiKey = connector._apiKey || null;\n\n // Step 7: Create connection object\n const connection = new RFWebRTCConnection(\n pc,\n remoteStreamPromise,\n pipelineId,\n apiKey,\n dataChannel,\n {\n localStream,\n uploadChannel,\n onData,\n onComplete\n }\n );\n\n // Step 8: Start file upload if applicable (runs in background)\n if (file && uploadChannel) {\n connection.startUpload(file, onFileUploadProgress).catch(err => {\n console.error(\"[RFWebRTC] Upload error:\", err);\n });\n }\n\n return connection;\n}\n\n/**\n * Main function to establish WebRTC streaming connection\n *\n * Creates a WebRTC connection to Roboflow for real-time inference on video streams.\n *\n * @param params - Connection parameters\n * @returns Promise resolving to RFWebRTCConnection\n *\n * @example\n * ```typescript\n * import { useStream } from 'inferencejs/webrtc';\n * import { connectors } from 'inferencejs/api';\n * import { useCamera } from 'inferencejs/streams';\n *\n * const connector = connectors.withApiKey(\"your-api-key\");\n * const stream = await useCamera({ video: { facingMode: { ideal: \"environment\" } } });\n * const conn = await useStream({\n * source: stream,\n * connector,\n * wrtcParams: {\n * workflowSpec: {\n * // Your workflow specification\n * },\n * imageInputName: \"image\",\n * streamOutputNames: [\"output\"],\n * dataOutputNames: [\"predictions\"]\n * },\n * onData: (data) => {\n * console.log(\"Inference results:\", data);\n * }\n * });\n *\n * const remoteStream = await conn.remoteStream();\n * videoElement.srcObject = remoteStream;\n * ```\n */\nexport async function useStream({\n source,\n connector,\n wrtcParams,\n onData,\n options = {}\n}: UseStreamParams): Promise<RFWebRTCConnection> {\n if (source instanceof File) {\n throw new Error(\"useStream requires a MediaStream. Use useVideoFile for File uploads.\");\n }\n\n return baseUseStream({\n source,\n connector,\n wrtcParams,\n onData,\n options\n });\n}\n\n/**\n * Parameters for useVideoFile function\n */\nexport interface UseVideoFileParams {\n /** The video file to upload */\n file: File;\n /** Connector for WebRTC signaling */\n connector: Connector;\n /** WebRTC parameters for the workflow */\n wrtcParams: WebRTCParams;\n /** Callback for inference results */\n onData?: (data: WebRTCOutputData) => void;\n /** Callback for upload progress */\n onUploadProgress?: (bytesUploaded: number, totalBytes: number) => void;\n /** Callback when processing completes (datachannel closes) */\n onComplete?: () => void;\n}\n\n/**\n * Upload a video file for batch inference processing\n *\n * Creates a WebRTC connection to Roboflow for uploading a video file\n * and receiving inference results. The file is uploaded via datachannel\n * with intelligent backpressure handling.\n *\n * @param params - Connection parameters\n * @returns Promise resolving to RFWebRTCConnection\n *\n * @example\n * ```typescript\n * import { connectors, webrtc } from '@roboflow/inference-sdk';\n *\n * const connector = connectors.withApiKey(\"your-api-key\");\n * const connection = await webrtc.useVideoFile({\n * file: videoFile,\n * connector,\n * wrtcParams: {\n * workflowSpec: { ... },\n * imageInputName: \"image\",\n * dataOutputNames: [\"predictions\"]\n * },\n * onData: (data) => {\n * console.log(\"Inference results:\", data);\n * if (data.processing_complete) {\n * console.log(\"Processing complete!\");\n * }\n * },\n * onUploadProgress: (uploaded, total) => {\n * console.log(`Upload: ${(uploaded / total * 100).toFixed(1)}%`);\n * }\n * });\n *\n * // When done\n * await connection.cleanup();\n * ```\n */\nexport async function useVideoFile({\n file,\n connector,\n wrtcParams,\n onData,\n onUploadProgress,\n onComplete\n}: UseVideoFileParams): Promise<RFWebRTCConnection> {\n return baseUseStream({\n source: file,\n connector,\n wrtcParams: {\n ...wrtcParams,\n realtimeProcessing: wrtcParams.realtimeProcessing ?? true\n },\n onData,\n onComplete,\n onFileUploadProgress: onUploadProgress\n });\n}\n"],"names":["_a","RF_API_BASE_URL","ROBOFLOW_SERVERLESS_URLS","InferenceHTTPClient","apiKey","serverUrl","__publicField","offer","workflowSpec","workspaceName","workflowId","config","hasWorkflowSpec","hasWorkspaceIdentifier","imageInputName","streamOutputNames","dataOutputNames","threadPoolWorkers","workflowsParameters","iceServers","processingTimeout","requestedPlan","requestedRegion","realtimeProcessing","workflowConfiguration","payload","response","errorText","pipelineId","turnConfig","iceServersRaw","server","err","connectors","options","client","wrtcParams","proxyUrl","turnConfigUrl","useCamera","constraints","stream","t","stopStream","track","CHUNK_SIZE","BUFFER_LIMIT","POLL_INTERVAL","sleep","ms","resolve","FileUploader","file","channel","onProgress","totalBytes","chunkIndex","start","end","chunkBlob","chunkData","message","view","HEADER_SIZE","ChunkReassembler","frameId","totalChunks","frame","totalLength","sum","chunk","result","offset","i","parseBinaryHeader","buffer","waitForIceGathering","pc","timeoutMs","hasSrflx","candidateHandler","event","check","reject","setupRemoteStreamListener","DEFAULT_ICE_SERVERS","preparePeerConnection","localStream","customIceServers","remoteStreamPromise","dataChannel","uploadChannel","disableInputStreamDownscaling","sender","s","params","waitForChannelOpen","openHandler","errorHandler","timeout","RFWebRTCConnection","onData","messageEvent","completePayload","jsonString","data","error","baseUseStream","source","connector","onComplete","onFileUploadProgress","isFile","resolvedWrtcParams","answer","sdpAnswer","checkState","connection","useStream","useVideoFile","onUploadProgress"],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","sources":["../src/inference-api.ts","../src/streams.ts","../src/video-upload.ts","../src/webrtc.ts"],"sourcesContent":["/**\n * Base URL for the Roboflow API (used for TURN server configuration)\n * Can be overridden via environment variable in Node.js environments\n */\nconst RF_API_BASE_URL = typeof process !== \"undefined\" && process.env?.RF_API_BASE_URL\n ? process.env.RF_API_BASE_URL\n : \"https://api.roboflow.com\";\n\n/**\n * List of known Roboflow serverless API URLs where auto TURN config applies\n */\nconst ROBOFLOW_SERVERLESS_URLS = [\n \"https://serverless.roboflow.com\"\n];\n\nexport interface WebRTCWorkerConfig {\n imageInputName?: string;\n streamOutputNames?: string[];\n dataOutputNames?: string[];\n threadPoolWorkers?: number;\n /**\n * Workflow parameters to pass to the workflow execution\n */\n workflowsParameters?: Record<string, any>;\n /**\n * ICE servers for WebRTC connections (used for both client and server)\n */\n iceServers?: RTCIceServerConfig[];\n /**\n * Processing timeout in seconds (serverless only)\n * @default 600\n */\n processingTimeout?: number;\n /**\n * Requested compute plan (serverless only)\n * @example \"webrtc-gpu-small\"\n */\n requestedPlan?: string;\n /**\n * Requested region for processing (serverless only)\n * @example \"us\"\n */\n requestedRegion?: string;\n /**\n * Set to false for file upload mode (batch processing).\n * When false, server processes all frames sequentially instead of dropping frames.\n * @default true\n */\n realtimeProcessing?: boolean;\n /**\n * RTSP URL for server-side video capture.\n * When provided, the server captures video from this RTSP stream instead of receiving\n * video from the client. Supports credentials in URL format: rtsp://user:pass@host/stream\n * @example \"rtsp://camera.local/stream\"\n */\n rtspUrl?: string;\n}\n\n/**\n * ICE server configuration for WebRTC connections\n *\n * Use this to configure custom STUN/TURN servers for users behind\n * symmetric NAT or restrictive firewalls.\n */\nexport interface RTCIceServerConfig {\n urls: string[];\n username?: string;\n credential?: string;\n}\n\nexport interface WebRTCOffer {\n sdp: string;\n type: string;\n}\n\nexport type WorkflowSpec = Record<string, any>;\n\nexport interface WebRTCWorkerResponse {\n status?: string;\n sdp: string;\n type: string;\n context?: {\n request_id: string | null;\n pipeline_id: string | null;\n };\n}\n\nexport interface WebRTCParams {\n workflowSpec?: WorkflowSpec;\n workspaceName?: string;\n workflowId?: string;\n imageInputName?: string;\n streamOutputNames?: string[];\n dataOutputNames?: string[];\n threadPoolWorkers?: number;\n /**\n * Workflow parameters to pass to the workflow execution\n */\n workflowsParameters?: Record<string, any>;\n /**\n * ICE servers for WebRTC connections (used for both client and server)\n *\n * Use this to specify custom STUN/TURN servers for users behind\n * symmetric NAT or restrictive firewalls. The same configuration is\n * used for both the client-side RTCPeerConnection and sent to the\n * server via webrtc_config.\n *\n * @example\n * ```typescript\n * iceServers: [\n * { urls: [\"stun:stun.l.google.com:19302\"] },\n * { urls: [\"turn:turn.example.com:3478\"], username: \"user\", credential: \"pass\" }\n * ]\n * ```\n */\n iceServers?: RTCIceServerConfig[];\n /**\n * Processing timeout in seconds (serverless only)\n * @default 600\n */\n processingTimeout?: number;\n /**\n * Requested compute plan (serverless only)\n * @example \"webrtc-gpu-small\"\n */\n requestedPlan?: string;\n /**\n * Requested region for processing (serverless only)\n * @example \"us\"\n */\n requestedRegion?: string;\n /**\n * Set to false for file upload mode (batch processing).\n * When false, server processes all frames sequentially instead of dropping frames.\n * @default true\n */\n realtimeProcessing?: boolean;\n /**\n * RTSP URL for server-side video capture.\n * When provided, the server captures video from this RTSP stream instead of receiving\n * video from the client. Supports credentials in URL format: rtsp://user:pass@host/stream\n * @example \"rtsp://camera.local/stream\"\n */\n rtspUrl?: string;\n}\n\nexport interface Connector {\n connectWrtc(offer: WebRTCOffer, wrtcParams: WebRTCParams): Promise<WebRTCWorkerResponse>;\n /**\n * Fetch ICE servers (TURN configuration) for WebRTC connections\n * This should be called BEFORE creating the RTCPeerConnection to ensure\n * proper NAT traversal configuration.\n *\n * @returns Promise resolving to ICE server configuration, or null/undefined if not available\n */\n getIceServers?(): Promise<RTCIceServerConfig[] | null>;\n _apiKey?: string;\n _serverUrl?: string;\n}\n\nexport class InferenceHTTPClient {\n private apiKey: string;\n private serverUrl: string;\n\n /**\n * @private\n * Use InferenceHTTPClient.init() instead\n */\n private constructor(apiKey: string, serverUrl: string = \"https://serverless.roboflow.com\") {\n this.apiKey = apiKey;\n this.serverUrl = serverUrl;\n }\n\n static init({ apiKey, serverUrl }: { apiKey: string; serverUrl?: string }): InferenceHTTPClient {\n if (!apiKey) {\n throw new Error(\"apiKey is required\");\n }\n return new InferenceHTTPClient(apiKey, serverUrl);\n }\n\n /**\n * Initialize a WebRTC worker pipeline\n *\n * @param params - Pipeline parameters\n * @param params.offer - WebRTC offer { sdp, type }\n * @param params.workflowSpec - Workflow specification\n * @param params.config - Additional configuration\n * @param params.config.imageInputName - Input image name (default: \"image\")\n * @param params.config.streamOutputNames - Output stream names for video (default: [])\n * @param params.config.dataOutputNames - Output data names (default: [\"string\"])\n * @param params.config.threadPoolWorkers - Thread pool workers (default: 4)\n * @returns Promise resolving to answer with SDP and pipeline ID\n *\n * @example\n * ```typescript\n * const answer = await client.initializeWebrtcWorker({\n * offer: { sdp, type },\n * workflowSpec: { ... },\n * config: {\n * imageInputName: \"image\",\n * streamOutputNames: [\"output_image\"]\n * }\n * });\n * ```\n */\n async initializeWebrtcWorker({\n offer,\n workflowSpec,\n workspaceName,\n workflowId,\n config = {}\n }: {\n offer: WebRTCOffer;\n workflowSpec?: WorkflowSpec;\n workspaceName?: string;\n workflowId?: string;\n config?: WebRTCWorkerConfig;\n }): Promise<WebRTCWorkerResponse> {\n if (!offer || !offer.sdp || !offer.type) {\n throw new Error(\"offer with sdp and type is required\");\n }\n\n // Validate that either workflowSpec OR (workspaceName + workflowId) is provided\n const hasWorkflowSpec = !!workflowSpec;\n const hasWorkspaceIdentifier = !!(workspaceName && workflowId);\n\n if (!hasWorkflowSpec && !hasWorkspaceIdentifier) {\n throw new Error(\"Either workflowSpec OR (workspaceName + workflowId) is required\");\n }\n if (hasWorkflowSpec && hasWorkspaceIdentifier) {\n throw new Error(\"Provide either workflowSpec OR (workspaceName + workflowId), not both\");\n }\n\n const {\n imageInputName = \"image\",\n streamOutputNames = [],\n dataOutputNames = [],\n threadPoolWorkers = 4,\n workflowsParameters = {},\n iceServers,\n processingTimeout,\n requestedPlan,\n requestedRegion,\n realtimeProcessing = true,\n rtspUrl\n } = config as any;\n\n // Build workflow_configuration based on what's provided\n const workflowConfiguration: any = {\n type: \"WorkflowConfiguration\",\n image_input_name: imageInputName,\n workflows_parameters: workflowsParameters,\n workflows_thread_pool_workers: threadPoolWorkers,\n cancel_thread_pool_tasks_on_exit: true,\n video_metadata_input_name: \"video_metadata\"\n };\n\n if (hasWorkflowSpec) {\n workflowConfiguration.workflow_specification = workflowSpec;\n } else {\n workflowConfiguration.workspace_name = workspaceName;\n workflowConfiguration.workflow_id = workflowId;\n }\n\n const payload: Record<string, any> = {\n workflow_configuration: workflowConfiguration,\n api_key: this.apiKey,\n webrtc_realtime_processing: realtimeProcessing,\n webrtc_offer: {\n sdp: offer.sdp,\n type: offer.type\n },\n webrtc_config: iceServers ? { iceServers } : null,\n stream_output: streamOutputNames,\n data_output: dataOutputNames\n };\n\n // Add serverless-specific fields if provided\n if (processingTimeout !== undefined) {\n payload.processing_timeout = processingTimeout;\n }\n if (requestedPlan !== undefined) {\n payload.requested_plan = requestedPlan;\n }\n if (requestedRegion !== undefined) {\n payload.requested_region = requestedRegion;\n }\n // Add RTSP URL for server-side video capture\n if (rtspUrl) {\n payload.rtsp_url = rtspUrl;\n }\n const response = await fetch(`${this.serverUrl}/initialise_webrtc_worker`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(payload)\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"\");\n throw new Error(`initialise_webrtc_worker failed (${response.status}): ${errorText}`);\n }\n\n const result = await response.json();\n\n return result;\n }\n\n async terminatePipeline({ pipelineId }: { pipelineId: string }): Promise<void> {\n if (!pipelineId) {\n throw new Error(\"pipelineId is required\");\n }\n\n await fetch(\n `${this.serverUrl}/inference_pipelines/${pipelineId}/terminate?api_key=${this.apiKey}`,\n {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" }\n }\n );\n }\n\n /**\n * Fetch TURN server configuration from Roboflow API\n *\n * This automatically fetches TURN server credentials for improved WebRTC\n * connectivity through firewalls and NAT. Only applicable when using\n * Roboflow serverless infrastructure.\n *\n * @returns Promise resolving to ICE server configuration, or null if not applicable\n *\n * @example\n * ```typescript\n * const client = InferenceHTTPClient.init({ apiKey: \"your-api-key\" });\n * const iceServers = await client.fetchTurnConfig();\n * // Returns: [{ urls: [\"turn:...\"], username: \"...\", credential: \"...\" }]\n * ```\n */\n async fetchTurnConfig(): Promise<RTCIceServerConfig[] | null> {\n // // Only fetch TURN config for Roboflow serverless URLs\n if (!ROBOFLOW_SERVERLESS_URLS.includes(this.serverUrl)) {\n return null;\n }\n try {\n const response = await fetch(\n `${RF_API_BASE_URL}/webrtc_turn_config?api_key=${this.apiKey}`,\n {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" }\n }\n );\n\n if (!response.ok) {\n console.warn(`[RFWebRTC] Failed to fetch TURN config (${response.status}), using defaults`);\n return null;\n }\n\n const turnConfig = await response.json();\n\n // Handle 3 formats:\n // 1. Single server object: { urls, username, credential }\n // 2. Array of servers: [{ urls, username, credential }, ...]\n // 3. Object with iceServers: { iceServers: [...] }\n let iceServersRaw: any[];\n\n if (Array.isArray(turnConfig)) {\n // Format 2: array of servers\n iceServersRaw = turnConfig;\n } else if (turnConfig.iceServers && Array.isArray(turnConfig.iceServers)) {\n // Format 3: object with iceServers array\n iceServersRaw = turnConfig.iceServers;\n } else if (turnConfig.urls) {\n // Format 1: single server object - wrap in array\n iceServersRaw = [turnConfig];\n } else {\n console.warn(\"[RFWebRTC] Invalid TURN config format, using defaults\");\n return null;\n }\n\n // Normalize the ICE servers format\n const iceServers: RTCIceServerConfig[] = iceServersRaw.map((server: any) => ({\n urls: Array.isArray(server.urls) ? server.urls : [server.urls],\n username: server.username,\n credential: server.credential\n }));\n\n return iceServers;\n } catch (err) {\n console.warn(\"[RFWebRTC] Error fetching TURN config:\", err);\n return null;\n }\n }\n}\n\n/**\n * Connectors for establishing WebRTC connections to Roboflow\n */\nexport const connectors = {\n /**\n * Create a connector that uses API key directly\n *\n * **WARNING**: If you use this in the frontend, it will expose your API key. \n * Use only for demos/testing.\n * For production, use withProxyUrl() with a backend proxy.\n *\n * @param apiKey - Roboflow API key\n * @param options - Additional options\n * @param options.serverUrl - Custom Roboflow server URL\n * @returns Connector with connectWrtc method\n *\n * @example\n * ```typescript\n * const connector = connectors.withApiKey(\"your-api-key\");\n * const answer = await connector.connectWrtc(offer, wrtcParams);\n * ```\n */\n withApiKey(apiKey: string, options: { serverUrl?: string } = {}): Connector {\n const { serverUrl } = options;\n\n // Warn if running in browser context\n if (typeof window !== 'undefined') {\n console.warn(\n '[Security Warning] Using API key directly in browser will expose it. ' +\n 'Use connectors.withProxyUrl() for production. ' +\n 'See: https://docs.roboflow.com/api-reference/authentication#securing-your-api-key'\n );\n }\n\n const client = InferenceHTTPClient.init({ apiKey, serverUrl });\n\n return {\n connectWrtc: async (offer: WebRTCOffer, wrtcParams: WebRTCParams): Promise<WebRTCWorkerResponse> => {\n console.debug(\"wrtcParams\", wrtcParams);\n const answer = await client.initializeWebrtcWorker({\n offer,\n workflowSpec: wrtcParams.workflowSpec,\n workspaceName: wrtcParams.workspaceName,\n workflowId: wrtcParams.workflowId,\n config: {\n imageInputName: wrtcParams.imageInputName,\n streamOutputNames: wrtcParams.streamOutputNames,\n dataOutputNames: wrtcParams.dataOutputNames,\n threadPoolWorkers: wrtcParams.threadPoolWorkers,\n workflowsParameters: wrtcParams.workflowsParameters,\n iceServers: wrtcParams.iceServers,\n processingTimeout: wrtcParams.processingTimeout,\n requestedPlan: wrtcParams.requestedPlan,\n requestedRegion: wrtcParams.requestedRegion,\n realtimeProcessing: wrtcParams.realtimeProcessing,\n rtspUrl: wrtcParams.rtspUrl\n }\n });\n\n return answer;\n },\n\n /**\n * Fetch TURN server configuration for improved WebRTC connectivity\n */\n getIceServers: async (): Promise<RTCIceServerConfig[] | null> => {\n return await client.fetchTurnConfig();\n },\n\n // Store apiKey for cleanup\n _apiKey: apiKey,\n _serverUrl: serverUrl\n };\n },\n\n /**\n * Create a connector that uses a backend proxy (recommended for production)\n *\n * Your backend receives the offer and wrtcParams, adds the secret API key,\n * and forwards to Roboflow. This keeps your API key secure.\n *\n * For improved WebRTC connectivity through firewalls, implement a separate\n * endpoint for TURN server configuration that calls `fetchTurnConfig()`.\n *\n * @param proxyUrl - Backend proxy endpoint URL for WebRTC initialization\n * @param options - Additional options\n * @param options.turnConfigUrl - Optional URL for fetching TURN server configuration\n * @returns Connector with connectWrtc and optional getIceServers methods\n *\n * @example\n * ```typescript\n * // Frontend: Create connector with TURN config endpoint\n * const connector = connectors.withProxyUrl('/api/init-webrtc', {\n * turnConfigUrl: '/api/turn-config'\n * });\n * ```\n *\n * @example\n * Backend implementation (Express) with TURN server support:\n * ```typescript\n * // Endpoint for TURN configuration (called first by SDK)\n * app.get('/api/turn-config', async (req, res) => {\n * const client = InferenceHTTPClient.init({\n * apiKey: process.env.ROBOFLOW_API_KEY\n * });\n * const iceServers = await client.fetchTurnConfig();\n * res.json({ iceServers });\n * });\n *\n * // Endpoint for WebRTC initialization\n * app.post('/api/init-webrtc', async (req, res) => {\n * const { offer, wrtcParams } = req.body;\n * const client = InferenceHTTPClient.init({\n * apiKey: process.env.ROBOFLOW_API_KEY\n * });\n *\n * const answer = await client.initializeWebrtcWorker({\n * offer,\n * workflowSpec: wrtcParams.workflowSpec,\n * workspaceName: wrtcParams.workspaceName,\n * workflowId: wrtcParams.workflowId,\n * config: {\n * imageInputName: wrtcParams.imageInputName,\n * streamOutputNames: wrtcParams.streamOutputNames,\n * dataOutputNames: wrtcParams.dataOutputNames,\n * threadPoolWorkers: wrtcParams.threadPoolWorkers,\n * workflowsParameters: wrtcParams.workflowsParameters,\n * iceServers: wrtcParams.iceServers,\n * processingTimeout: wrtcParams.processingTimeout,\n * requestedPlan: wrtcParams.requestedPlan,\n * requestedRegion: wrtcParams.requestedRegion\n * }\n * });\n *\n * res.json(answer);\n * });\n * ```\n */\n withProxyUrl(proxyUrl: string, options: { turnConfigUrl?: string } = {}): Connector {\n const { turnConfigUrl } = options;\n\n return {\n connectWrtc: async (offer: WebRTCOffer, wrtcParams: WebRTCParams): Promise<WebRTCWorkerResponse> => {\n const response = await fetch(proxyUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n offer,\n wrtcParams\n })\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"\");\n throw new Error(`Proxy request failed (${response.status}): ${errorText}`);\n }\n\n return await response.json();\n },\n\n /**\n * Fetch TURN server configuration from the proxy backend\n * Only available if turnConfigUrl was provided\n */\n getIceServers: turnConfigUrl\n ? async (): Promise<RTCIceServerConfig[] | null> => {\n try {\n const response = await fetch(turnConfigUrl, {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" }\n });\n\n if (!response.ok) {\n console.warn(`[RFWebRTC] Failed to fetch TURN config from proxy (${response.status})`);\n return null;\n }\n\n const data = await response.json();\n return data.iceServers || null;\n } catch (err) {\n console.warn(\"[RFWebRTC] Error fetching TURN config from proxy:\", err);\n return null;\n }\n }\n : undefined\n };\n }\n};\n","/**\n * Get a camera stream with the given constraints.\n *\n * @param constraints - MediaStreamConstraints for getUserMedia\n * @returns Promise that resolves to MediaStream\n *\n * @example\n * ```typescript\n * const stream = await useCamera({\n * video: {\n * facingMode: { ideal: \"user\" },\n * width: { ideal: 1280 },\n * height: { ideal: 720 },\n * frameRate: { ideal: 30 }\n * },\n * audio: false\n * });\n * ```\n */\nexport async function useCamera(constraints: MediaStreamConstraints = { video: true }): Promise<MediaStream> {\n try {\n console.log(\"[RFStreams] requesting with\", constraints);\n const stream = await navigator.mediaDevices.getUserMedia(constraints);\n console.log(\"[RFStreams] got stream\", stream.getVideoTracks().map(t => ({ id: t.id, label: t.label })));\n return stream;\n } catch (err) {\n console.warn(\"[RFStreams] failed, falling back\", err);\n const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });\n console.log(\"[RFStreams] fallback stream\", stream.getVideoTracks().map(t => ({ id: t.id, label: t.label })));\n return stream;\n }\n}\n \nexport function stopStream(stream: MediaStream | null | undefined): void {\n if (stream) {\n stream.getTracks().forEach(track => track.stop());\n console.log(\"[RFStreams] Stream stopped\");\n }\n}\n","/**\n * Video file upload via WebRTC datachannel\n *\n * This module provides the FileUploader class for chunked file uploads\n * through WebRTC datachannels with backpressure handling.\n */\n\n/**\n * Configuration constants for file upload (matching Python SDK)\n */\nconst CHUNK_SIZE = 49152; // 49KB - safe for WebRTC\nconst BUFFER_LIMIT = 262144; // 256KB - backpressure threshold\nconst POLL_INTERVAL = 10; // 10ms buffer check interval\n\n/**\n * Helper to sleep for a given number of milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * FileUploader handles chunked file upload with backpressure\n *\n * Uploads files through a WebRTC datachannel in 49KB chunks with\n * intelligent backpressure handling to prevent overwhelming the network.\n */\nexport class FileUploader {\n private file: File;\n private channel: RTCDataChannel;\n private totalChunks: number;\n private cancelled: boolean = false;\n\n constructor(file: File, channel: RTCDataChannel) {\n this.file = file;\n this.channel = channel;\n this.totalChunks = Math.ceil(file.size / CHUNK_SIZE);\n }\n\n /**\n * Cancel the upload\n */\n cancel(): void {\n this.cancelled = true;\n }\n\n /**\n * Upload the file in chunks with backpressure handling\n *\n * @param onProgress - Optional callback for progress updates (bytesUploaded, totalBytes)\n */\n async upload(onProgress?: (bytesUploaded: number, totalBytes: number) => void): Promise<void> {\n const totalBytes = this.file.size;\n\n for (let chunkIndex = 0; chunkIndex < this.totalChunks; chunkIndex++) {\n // Check for cancellation\n if (this.cancelled) {\n throw new Error(\"Upload cancelled\");\n }\n\n // Check channel state\n if (this.channel.readyState !== \"open\") {\n throw new Error(\"Video upload interrupted\");\n }\n\n // Read chunk from file\n const start = chunkIndex * CHUNK_SIZE;\n const end = Math.min(start + CHUNK_SIZE, totalBytes);\n const chunkBlob = this.file.slice(start, end);\n const chunkData = new Uint8Array(await chunkBlob.arrayBuffer());\n\n // Create message with 8-byte header (chunkIndex + totalChunks as uint32 LE)\n const message = new ArrayBuffer(8 + chunkData.length);\n const view = new DataView(message);\n view.setUint32(0, chunkIndex, true); // little-endian\n view.setUint32(4, this.totalChunks, true); // little-endian\n new Uint8Array(message, 8).set(chunkData);\n\n // Backpressure: wait for buffer to drain\n while (this.channel.bufferedAmount > BUFFER_LIMIT) {\n if (this.channel.readyState !== \"open\") {\n throw new Error(\"Video upload interrupted\");\n }\n await sleep(POLL_INTERVAL);\n }\n\n // Send chunk\n this.channel.send(message);\n\n // Report progress\n if (onProgress) {\n onProgress(end, totalBytes);\n }\n }\n }\n}\n","\nimport { InferenceHTTPClient, Connector, WebRTCParams, RTCIceServerConfig } from \"./inference-api\";\nimport { stopStream } from \"./streams\";\nimport { WebRTCOutputData, WebRTCHooks } from \"./webrtc-types\";\nimport { FileUploader } from \"./video-upload\";\n\n// Re-export shared types\nexport type { WebRTCVideoMetadata, WebRTCOutputData, WebRTCHooks } from \"./webrtc-types\";\n\n// Re-export FileUploader from video-upload\nexport { FileUploader } from \"./video-upload\";\n\n/**\n * Binary protocol header size (frame_id + chunk_index + total_chunks)\n * Each field is 4 bytes uint32 little-endian\n */\nconst HEADER_SIZE = 12;\n\n/**\n * Reassembles chunked binary messages from the datachannel\n */\nexport class ChunkReassembler {\n private pendingFrames: Map<number, {\n chunks: Map<number, Uint8Array>;\n totalChunks: number;\n }> = new Map();\n\n /**\n * Process an incoming chunk and return the complete message if all chunks received\n */\n processChunk(frameId: number, chunkIndex: number, totalChunks: number, payload: Uint8Array): Uint8Array | null {\n // Single chunk message - return immediately\n if (totalChunks === 1) {\n return payload;\n }\n\n // Multi-chunk message - accumulate\n if (!this.pendingFrames.has(frameId)) {\n this.pendingFrames.set(frameId, {\n chunks: new Map(),\n totalChunks\n });\n }\n\n const frame = this.pendingFrames.get(frameId)!;\n frame.chunks.set(chunkIndex, payload);\n\n // Check if all chunks received\n if (frame.chunks.size === totalChunks) {\n // Reassemble in order\n const totalLength = Array.from(frame.chunks.values()).reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n\n for (let i = 0; i < totalChunks; i++) {\n const chunk = frame.chunks.get(i)!;\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n this.pendingFrames.delete(frameId);\n return result;\n }\n\n return null;\n }\n\n /**\n * Clear all pending frames (for cleanup)\n */\n clear(): void {\n this.pendingFrames.clear();\n }\n}\n\n/**\n * Parse the binary header from a datachannel message\n */\nexport function parseBinaryHeader(buffer: ArrayBuffer): { frameId: number; chunkIndex: number; totalChunks: number; payload: Uint8Array } {\n const view = new DataView(buffer);\n const frameId = view.getUint32(0, true); // little-endian\n const chunkIndex = view.getUint32(4, true); // little-endian\n const totalChunks = view.getUint32(8, true); // little-endian\n const payload = new Uint8Array(buffer, HEADER_SIZE);\n\n return { frameId, chunkIndex, totalChunks, payload };\n}\n\nexport interface UseStreamOptions {\n disableInputStreamDownscaling?: boolean;\n}\n\nexport interface UseStreamParams {\n source: MediaStream;\n connector: Connector;\n wrtcParams: WebRTCParams;\n onData?: (data: WebRTCOutputData) => void;\n options?: UseStreamOptions;\n /** Lifecycle hooks for customizing WebRTC behavior */\n hooks?: WebRTCHooks;\n}\n\nasync function waitForIceGathering(pc: RTCPeerConnection, timeoutMs = 6000): Promise<void> {\n if (pc.iceGatheringState === \"complete\") return;\n\n let hasSrflx = false;\n\n // Track if we get a good candidate (srflx = public IP via STUN)\n const candidateHandler = (event: RTCPeerConnectionIceEvent) => {\n if (event.candidate && event.candidate.type === \"srflx\") {\n hasSrflx = true;\n }\n };\n pc.addEventListener(\"icecandidate\", candidateHandler);\n\n try {\n await Promise.race([\n new Promise<void>(resolve => {\n const check = () => {\n if (pc.iceGatheringState === \"complete\") {\n pc.removeEventListener(\"icegatheringstatechange\", check);\n resolve();\n }\n };\n pc.addEventListener(\"icegatheringstatechange\", check);\n }),\n new Promise<void>((resolve, reject) => {\n setTimeout(() => {\n if (!hasSrflx) {\n console.error(\"[ICE] timeout with NO srflx candidate! Connection may fail.\");\n reject(new Error(\"ICE gathering timeout without srflx candidate\"));\n } else {\n resolve();\n }\n }, timeoutMs);\n })\n ]);\n } finally {\n pc.removeEventListener(\"icecandidate\", candidateHandler);\n }\n}\n\nfunction setupRemoteStreamListener(pc: RTCPeerConnection): Promise<MediaStream> {\n return new Promise((resolve) => {\n pc.addEventListener(\"track\", (event: RTCTrackEvent) => {\n if (event.streams && event.streams[0]) {\n resolve(event.streams[0]);\n }\n });\n });\n}\n\nconst DEFAULT_ICE_SERVERS: RTCIceServerConfig[] = [\n { urls: [\"stun:stun.l.google.com:19302\"] }\n];\n\nasync function preparePeerConnection(\n localStream?: MediaStream,\n file?: File,\n customIceServers?: RTCIceServerConfig[],\n rtspUrl?: string,\n hooks?: WebRTCHooks\n): Promise<{\n pc: RTCPeerConnection;\n offer: RTCSessionDescriptionInit;\n remoteStreamPromise: Promise<MediaStream>;\n dataChannel: RTCDataChannel;\n uploadChannel?: RTCDataChannel;\n}> {\n // Validate: exactly one source type must be provided\n const hasLocalStream = !!localStream;\n const hasFile = !!file;\n const hasRtspUrl = !!rtspUrl;\n const sourceCount = [hasLocalStream, hasFile, hasRtspUrl].filter(Boolean).length;\n\n if (sourceCount !== 1) {\n throw new Error(\"Exactly one of localStream, file, or rtspUrl must be provided\");\n }\n\n const iceServers = customIceServers ?? DEFAULT_ICE_SERVERS;\n\n const pc = new RTCPeerConnection({\n iceServers: iceServers as RTCIceServer[]\n });\n\n // Call onPeerConnectionCreated hook\n if (hooks?.onPeerConnectionCreated) {\n await hooks.onPeerConnectionCreated(pc);\n }\n\n // Add transceiver for receiving remote video (BEFORE adding tracks - order matters!)\n try {\n pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n } catch (err) {\n console.warn(\"[RFWebRTC] Could not add transceiver:\", err);\n }\n\n if (localStream) {\n // Add local tracks\n for (const track of localStream.getVideoTracks()) {\n const sender = pc.addTrack(track, localStream);\n\n // Call onTrackAdded hook\n if (hooks?.onTrackAdded) {\n await hooks.onTrackAdded(track, sender, pc);\n }\n }\n }\n // Note: For RTSP, no local tracks are added (receive-only mode)\n\n // Setup remote stream listener\n const remoteStreamPromise = setupRemoteStreamListener(pc);\n\n // Create control datachannel (named \"inference\" to match Python SDK)\n const dataChannel = pc.createDataChannel(\"inference\", {\n ordered: true\n });\n\n // Create upload datachannel for file uploads (not needed for RTSP)\n let uploadChannel: RTCDataChannel | undefined;\n if (file) {\n uploadChannel = pc.createDataChannel(\"video_upload\");\n }\n\n // Create offer\n let offer = await pc.createOffer();\n\n // Call onOfferCreated hook to allow SDP modification\n if (hooks?.onOfferCreated) {\n const modifiedOffer = await hooks.onOfferCreated(offer);\n if (modifiedOffer) {\n offer = modifiedOffer;\n }\n }\n\n await pc.setLocalDescription(offer);\n\n // Wait for ICE gathering\n await waitForIceGathering(pc);\n\n return {\n pc,\n offer: pc.localDescription!,\n remoteStreamPromise,\n dataChannel,\n uploadChannel\n };\n}\n\n/**\n * Disable input stream downscaling\n * @private\n */\nasync function disableInputStreamDownscaling(pc: RTCPeerConnection): Promise<void> {\n const sender = pc.getSenders().find(s => s.track && s.track.kind === \"video\");\n if (!sender) return;\n\n const params = sender.getParameters();\n params.encodings = params.encodings || [{}];\n params.encodings[0].scaleResolutionDownBy = 1;\n\n try {\n await sender.setParameters(params);\n } catch (err) {\n console.warn(\"[RFWebRTC] Failed to set encoding parameters:\", err);\n }\n}\n\n/**\n * Helper to wait for datachannel to open\n */\nfunction waitForChannelOpen(channel: RTCDataChannel, timeoutMs = 30000): Promise<void> {\n return new Promise((resolve, reject) => {\n if (channel.readyState === \"open\") {\n resolve();\n return;\n }\n\n const openHandler = () => {\n channel.removeEventListener(\"open\", openHandler);\n channel.removeEventListener(\"error\", errorHandler);\n clearTimeout(timeout);\n resolve();\n };\n\n const errorHandler = () => {\n channel.removeEventListener(\"open\", openHandler);\n channel.removeEventListener(\"error\", errorHandler);\n clearTimeout(timeout);\n reject(new Error(\"Datachannel error\"));\n };\n\n const timeout = setTimeout(() => {\n channel.removeEventListener(\"open\", openHandler);\n channel.removeEventListener(\"error\", errorHandler);\n reject(new Error(\"Datachannel open timeout\"));\n }, timeoutMs);\n\n channel.addEventListener(\"open\", openHandler);\n channel.addEventListener(\"error\", errorHandler);\n });\n}\n\n/**\n * WebRTC Connection object\n *\n * Represents an active WebRTC connection to Roboflow for streaming inference\n * or file-based batch processing.\n */\nexport class RFWebRTCConnection {\n /**\n * The underlying RTCPeerConnection.\n * Exposed for advanced use cases like getting stats or accessing senders.\n */\n public readonly peerConnection: RTCPeerConnection;\n private _localStream?: MediaStream;\n private remoteStreamPromise: Promise<MediaStream>;\n private pipelineId: string | null;\n private apiKey: string | null;\n /**\n * The data channel used for receiving inference results.\n * Exposed for advanced use cases.\n */\n public readonly dataChannel: RTCDataChannel;\n private reassembler: ChunkReassembler;\n /**\n * The data channel used for uploading video files (only available in file upload mode).\n * Exposed for advanced use cases.\n */\n public readonly uploadChannel?: RTCDataChannel;\n private uploader?: FileUploader;\n private onComplete?: () => void;\n\n /** @private */\n constructor(\n pc: RTCPeerConnection,\n remoteStreamPromise: Promise<MediaStream>,\n pipelineId: string | null,\n apiKey: string | null,\n dataChannel: RTCDataChannel,\n options?: {\n localStream?: MediaStream;\n uploadChannel?: RTCDataChannel;\n onData?: (data: any) => void;\n onComplete?: () => void;\n }\n ) {\n this.peerConnection = pc;\n this._localStream = options?.localStream;\n this.remoteStreamPromise = remoteStreamPromise;\n this.pipelineId = pipelineId;\n this.apiKey = apiKey;\n this.dataChannel = dataChannel;\n this.reassembler = new ChunkReassembler();\n this.uploadChannel = options?.uploadChannel;\n this.onComplete = options?.onComplete;\n\n // Set binary mode for datachannel\n this.dataChannel.binaryType = \"arraybuffer\";\n\n const onData = options?.onData;\n\n // Setup data channel event listeners\n if (onData) {\n this.dataChannel.addEventListener(\"message\", (messageEvent: MessageEvent) => {\n try {\n // Handle binary protocol with chunking\n if (messageEvent.data instanceof ArrayBuffer) {\n const { frameId, chunkIndex, totalChunks, payload } = parseBinaryHeader(messageEvent.data);\n const completePayload = this.reassembler.processChunk(frameId, chunkIndex, totalChunks, payload);\n\n if (completePayload) {\n // Decode UTF-8 JSON payload\n const decoder = new TextDecoder(\"utf-8\");\n const jsonString = decoder.decode(completePayload);\n const data = JSON.parse(jsonString);\n onData(data);\n }\n } else {\n // Fallback for string messages (shouldn't happen with new protocol)\n const data = JSON.parse(messageEvent.data);\n onData(data);\n }\n } catch (err) {\n console.error(\"[RFWebRTC] Failed to parse data channel message:\", err);\n }\n });\n\n this.dataChannel.addEventListener(\"error\", (error) => {\n console.error(\"[RFWebRTC] Data channel error:\", error);\n });\n }\n\n // Handle channel close - call onComplete when processing finishes\n this.dataChannel.addEventListener(\"close\", () => {\n this.reassembler.clear();\n if (this.onComplete) {\n this.onComplete();\n }\n });\n }\n\n /**\n * Get the remote stream (processed video from Roboflow)\n *\n * @returns Promise resolving to the remote MediaStream\n *\n * @example\n * ```typescript\n * const conn = await useStream({ ... });\n * const remoteStream = await conn.remoteStream();\n * videoElement.srcObject = remoteStream;\n * ```\n */\n async remoteStream(): Promise<MediaStream> {\n return await this.remoteStreamPromise;\n }\n\n /**\n * Get the local stream (original camera)\n *\n * @returns The local MediaStream, or undefined if using file upload mode\n *\n * @example\n * ```typescript\n * const conn = await useStream({ ... });\n * const localStream = conn.localStream();\n * if (localStream) {\n * videoElement.srcObject = localStream;\n * }\n * ```\n */\n localStream(): MediaStream | undefined {\n return this._localStream;\n }\n\n /**\n * Cleanup and close connection\n *\n * Terminates the pipeline on Roboflow, closes the peer connection,\n * and stops the local media stream (if applicable).\n *\n * @returns Promise that resolves when cleanup is complete\n *\n * @example\n * ```typescript\n * const conn = await useStream({ ... });\n * // ... use connection ...\n * await conn.cleanup(); // Clean up when done\n * ```\n */\n async cleanup(): Promise<void> {\n // Cancel any ongoing upload\n if (this.uploader) {\n this.uploader.cancel();\n }\n\n // Clear pending chunks\n this.reassembler.clear();\n\n // Terminate pipeline\n if (this.pipelineId && this.apiKey) {\n try {\n const client = InferenceHTTPClient.init({ apiKey: this.apiKey });\n await client.terminatePipeline({ pipelineId: this.pipelineId });\n } catch (err) {\n console.warn(\"[RFWebRTC] Failed to terminate pipeline:\", err);\n }\n }\n\n // Close peer connection\n if (this.peerConnection && this.peerConnection.connectionState !== \"closed\") {\n this.peerConnection.close();\n }\n\n // Stop local stream if present\n if (this._localStream) {\n stopStream(this._localStream);\n }\n }\n\n /**\n * Start uploading a file through the connection\n *\n * @param file - The file to upload\n * @param onProgress - Optional callback for progress updates (bytesUploaded, totalBytes)\n * @returns Promise that resolves when upload is complete\n * @throws Error if no upload channel is available\n *\n * @example\n * ```typescript\n * await connection.startUpload(videoFile, (uploaded, total) => {\n * console.log(`Upload progress: ${(uploaded / total * 100).toFixed(1)}%`);\n * });\n * ```\n */\n async startUpload(file: File, onProgress?: (bytesUploaded: number, totalBytes: number) => void): Promise<void> {\n if (!this.uploadChannel) {\n throw new Error(\"No upload channel available. This connection was not created for file uploads.\");\n }\n\n // Wait for upload channel to open\n await waitForChannelOpen(this.uploadChannel);\n\n this.uploader = new FileUploader(file, this.uploadChannel);\n await this.uploader.upload(onProgress);\n }\n\n /**\n * Cancel any ongoing file upload\n */\n cancelUpload(): void {\n if (this.uploader) {\n this.uploader.cancel();\n }\n }\n\n /**\n * Reconfigure pipeline outputs at runtime\n *\n * Dynamically change stream and data outputs without restarting the connection.\n * Set a field to `null` to leave it unchanged, or to `null` value to enable all outputs,\n * or to `[]` to disable/auto-detect.\n *\n * @param config - Output configuration\n * @param config.streamOutput - Stream output names (null = unchanged, [] = auto-detect, [\"name\"] = specific output)\n * @param config.dataOutput - Data output names (null = unchanged, [] = disable, [\"name\"] = specific outputs, null value = all outputs)\n *\n * @example\n * ```typescript\n * // Change to different stream output\n * connection.reconfigureOutputs({\n * streamOutput: [\"annotated_image\"],\n * dataOutput: null // unchanged\n * });\n *\n * // Enable all data outputs\n * connection.reconfigureOutputs({\n * streamOutput: null, // unchanged\n * dataOutput: null // null value = all outputs\n * });\n *\n * // Disable all data outputs\n * connection.reconfigureOutputs({\n * streamOutput: null, // unchanged\n * dataOutput: [] // empty array = disable\n * });\n * ```\n */\n reconfigureOutputs(config: { streamOutput?: string[] | null; dataOutput?: string[] | null }): void {\n const message: any = {};\n\n if (config.streamOutput !== undefined) {\n message.stream_output = config.streamOutput;\n }\n\n if (config.dataOutput !== undefined) {\n message.data_output = config.dataOutput;\n }\n\n this.sendData(message);\n }\n\n /**\n * Send data through the data channel\n * @private\n */\n private sendData(data: any): void {\n if (this.dataChannel.readyState !== \"open\") {\n console.warn(\"[RFWebRTC] Data channel is not open. Current state:\", this.dataChannel.readyState);\n return;\n }\n\n try {\n const message = typeof data === \"string\" ? data : JSON.stringify(data);\n this.dataChannel.send(message);\n } catch (err) {\n console.error(\"[RFWebRTC] Failed to send data:\", err);\n }\n }\n}\n\n/**\n * Internal base function for establishing WebRTC connection\n * Used by both useStream and useVideoFile\n * @private\n */\ninterface BaseUseStreamParams {\n source?: MediaStream | File;\n rtspUrl?: string;\n connector: Connector;\n wrtcParams: WebRTCParams;\n onData?: (data: WebRTCOutputData) => void;\n onComplete?: () => void;\n onFileUploadProgress?: (bytesUploaded: number, totalBytes: number) => void;\n options?: UseStreamOptions;\n hooks?: WebRTCHooks;\n}\n\nasync function baseUseStream({\n source,\n rtspUrl,\n connector,\n wrtcParams,\n onData,\n onComplete,\n onFileUploadProgress,\n options = {},\n hooks\n}: BaseUseStreamParams): Promise<RFWebRTCConnection> {\n // Validate connector\n if (!connector || typeof connector.connectWrtc !== \"function\") {\n throw new Error(\"connector must have a connectWrtc method\");\n }\n\n // Determine source type\n const isRtsp = !!rtspUrl;\n const isFile = !isRtsp && source instanceof File;\n const localStream = !isRtsp && !isFile && source ? (source as MediaStream) : undefined;\n const file = isFile ? (source as File) : undefined;\n\n // Step 1: Determine ICE servers to use\n // Priority: 1) User-provided in wrtcParams, 2) From connector.getIceServers(), 3) Defaults\n let iceServers = wrtcParams.iceServers;\n if ((!iceServers || iceServers.length === 0) && connector.getIceServers) {\n try {\n const turnConfig = await connector.getIceServers();\n if (turnConfig && turnConfig.length > 0) {\n iceServers = turnConfig;\n console.log(\"[RFWebRTC] Using TURN servers from connector\");\n }\n } catch (err) {\n console.warn(\"[RFWebRTC] Failed to fetch TURN config, using defaults:\", err);\n }\n }\n\n // Step 2: Prepare peer connection and create offer\n const { pc, offer, remoteStreamPromise, dataChannel, uploadChannel } = await preparePeerConnection(\n localStream,\n file,\n iceServers,\n rtspUrl,\n hooks\n );\n\n // Update wrtcParams with resolved iceServers so server also uses them\n // For file uploads, default to batch mode (realtimeProcessing: false)\n // For RTSP, default to realtime processing\n const resolvedWrtcParams = {\n ...wrtcParams,\n iceServers: iceServers,\n realtimeProcessing: wrtcParams.realtimeProcessing ?? !isFile,\n rtspUrl: rtspUrl\n };\n\n // Step 3: Call connector.connectWrtc to exchange SDP and get answer\n const answer = await connector.connectWrtc(\n { sdp: offer.sdp!, type: offer.type! },\n resolvedWrtcParams\n );\n\n // API returns sdp and type at root level\n const sdpAnswer = { sdp: answer.sdp, type: answer.type } as RTCSessionDescriptionInit;\n\n if (!sdpAnswer?.sdp || !sdpAnswer?.type) {\n console.error(\"[RFWebRTC] Invalid answer from server:\", answer);\n throw new Error(\"connector.connectWrtc must return answer with sdp and type\");\n }\n\n const pipelineId = answer?.context?.pipeline_id || null;\n\n // Step 4: Set remote description\n await pc.setRemoteDescription(sdpAnswer);\n\n // Step 5: Wait for connection to establish\n await new Promise<void>((resolve, reject) => {\n const checkState = () => {\n if (pc.connectionState === \"connected\") {\n pc.removeEventListener(\"connectionstatechange\", checkState);\n resolve();\n } else if (pc.connectionState === \"failed\") {\n pc.removeEventListener(\"connectionstatechange\", checkState);\n reject(new Error(\"WebRTC connection failed\"));\n }\n };\n\n pc.addEventListener(\"connectionstatechange\", checkState);\n checkState(); // Check immediately in case already connected\n\n // Timeout after 30 seconds\n setTimeout(() => {\n pc.removeEventListener(\"connectionstatechange\", checkState);\n reject(new Error(\"WebRTC connection timeout after 30s\"));\n }, 30000);\n });\n\n // Step 6: Optimize quality for MediaStream (disable downsampling by default)\n if (localStream) {\n const shouldDisableDownscaling = options.disableInputStreamDownscaling !== false;\n if (shouldDisableDownscaling) {\n await disableInputStreamDownscaling(pc);\n }\n }\n\n // Get apiKey from connector if available (for cleanup)\n const apiKey = connector._apiKey || null;\n\n // Step 7: Create connection object\n const connection = new RFWebRTCConnection(\n pc,\n remoteStreamPromise,\n pipelineId,\n apiKey,\n dataChannel,\n {\n localStream,\n uploadChannel,\n onData,\n onComplete\n }\n );\n\n // Step 8: Start file upload if applicable (runs in background)\n if (file && uploadChannel) {\n connection.startUpload(file, onFileUploadProgress).catch(err => {\n console.error(\"[RFWebRTC] Upload error:\", err);\n });\n }\n\n return connection;\n}\n\n/**\n * Main function to establish WebRTC streaming connection\n *\n * Creates a WebRTC connection to Roboflow for real-time inference on video streams.\n *\n * @param params - Connection parameters\n * @returns Promise resolving to RFWebRTCConnection\n *\n * @example\n * ```typescript\n * import { useStream } from 'inferencejs/webrtc';\n * import { connectors } from 'inferencejs/api';\n * import { useCamera } from 'inferencejs/streams';\n *\n * const connector = connectors.withApiKey(\"your-api-key\");\n * const stream = await useCamera({ video: { facingMode: { ideal: \"environment\" } } });\n * const conn = await useStream({\n * source: stream,\n * connector,\n * wrtcParams: {\n * workflowSpec: {\n * // Your workflow specification\n * },\n * imageInputName: \"image\",\n * streamOutputNames: [\"output\"],\n * dataOutputNames: [\"predictions\"]\n * },\n * onData: (data) => {\n * console.log(\"Inference results:\", data);\n * }\n * });\n *\n * const remoteStream = await conn.remoteStream();\n * videoElement.srcObject = remoteStream;\n * ```\n */\nexport async function useStream({\n source,\n connector,\n wrtcParams,\n onData,\n options = {},\n hooks\n}: UseStreamParams): Promise<RFWebRTCConnection> {\n if (source instanceof File) {\n throw new Error(\"useStream requires a MediaStream. Use useVideoFile for File uploads.\");\n }\n\n return baseUseStream({\n source,\n connector,\n wrtcParams,\n onData,\n options,\n hooks\n });\n}\n\n/**\n * Parameters for useVideoFile function\n */\nexport interface UseVideoFileParams {\n /** The video file to upload */\n file: File;\n /** Connector for WebRTC signaling */\n connector: Connector;\n /** WebRTC parameters for the workflow */\n wrtcParams: WebRTCParams;\n /** Callback for inference results */\n onData?: (data: WebRTCOutputData) => void;\n /** Callback for upload progress */\n onUploadProgress?: (bytesUploaded: number, totalBytes: number) => void;\n /** Callback when processing completes (datachannel closes) */\n onComplete?: () => void;\n /** Lifecycle hooks for customizing WebRTC behavior */\n hooks?: WebRTCHooks;\n}\n\n/**\n * Upload a video file for batch inference processing\n *\n * Creates a WebRTC connection to Roboflow for uploading a video file\n * and receiving inference results. The file is uploaded via datachannel\n * with intelligent backpressure handling.\n *\n * @param params - Connection parameters\n * @returns Promise resolving to RFWebRTCConnection\n *\n * @example\n * ```typescript\n * import { connectors, webrtc } from '@roboflow/inference-sdk';\n *\n * const connector = connectors.withApiKey(\"your-api-key\");\n * const connection = await webrtc.useVideoFile({\n * file: videoFile,\n * connector,\n * wrtcParams: {\n * workflowSpec: { ... },\n * imageInputName: \"image\",\n * dataOutputNames: [\"predictions\"]\n * },\n * onData: (data) => {\n * console.log(\"Inference results:\", data);\n * if (data.processing_complete) {\n * console.log(\"Processing complete!\");\n * }\n * },\n * onUploadProgress: (uploaded, total) => {\n * console.log(`Upload: ${(uploaded / total * 100).toFixed(1)}%`);\n * }\n * });\n *\n * // When done\n * await connection.cleanup();\n * ```\n */\nexport async function useVideoFile({\n file,\n connector,\n wrtcParams,\n onData,\n onUploadProgress,\n onComplete,\n hooks\n}: UseVideoFileParams): Promise<RFWebRTCConnection> {\n return baseUseStream({\n source: file,\n connector,\n wrtcParams: {\n ...wrtcParams,\n realtimeProcessing: wrtcParams.realtimeProcessing ?? true\n },\n onData,\n onComplete,\n onFileUploadProgress: onUploadProgress,\n hooks\n });\n}\n\n/**\n * Parameters for useRtspStream function\n */\nexport interface UseRtspStreamParams {\n /** RTSP URL for server-side video capture (e.g., \"rtsp://camera.local/stream\") */\n rtspUrl: string;\n /** Connector for WebRTC signaling */\n connector: Connector;\n /** WebRTC parameters for the workflow */\n wrtcParams: WebRTCParams;\n /** Callback for inference results */\n onData?: (data: WebRTCOutputData) => void;\n /** Lifecycle hooks for customizing WebRTC behavior */\n hooks?: WebRTCHooks;\n}\n\n/**\n * Connect to an RTSP stream for inference processing\n *\n * Creates a WebRTC connection where the server captures video from an RTSP URL\n * and sends processed video back to the client. This is a receive-only mode -\n * no video is sent from the browser to the server.\n *\n * @param params - Connection parameters\n * @returns Promise resolving to RFWebRTCConnection\n *\n * @example\n * ```typescript\n * import { connectors, webrtc } from '@roboflow/inference-sdk';\n *\n * const connector = connectors.withApiKey(\"your-api-key\");\n * const connection = await webrtc.useRtspStream({\n * rtspUrl: \"rtsp://camera.local/stream\",\n * connector,\n * wrtcParams: {\n * workflowSpec: { ... },\n * imageInputName: \"image\",\n * dataOutputNames: [\"predictions\"]\n * },\n * onData: (data) => {\n * console.log(\"Inference results:\", data);\n * }\n * });\n *\n * // Get processed video stream from server\n * const remoteStream = await connection.remoteStream();\n * videoElement.srcObject = remoteStream;\n *\n * // When done\n * await connection.cleanup();\n * ```\n */\nexport async function useRtspStream({\n rtspUrl,\n connector,\n wrtcParams,\n onData,\n hooks\n}: UseRtspStreamParams): Promise<RFWebRTCConnection> {\n // Validate RTSP URL format\n if (!rtspUrl.startsWith(\"rtsp://\") && !rtspUrl.startsWith(\"rtsps://\")) {\n throw new Error(\"Invalid RTSP URL: must start with rtsp:// or rtsps://\");\n }\n\n return baseUseStream({\n rtspUrl,\n connector,\n wrtcParams,\n onData,\n hooks\n });\n}\n"],"names":["_a","RF_API_BASE_URL","ROBOFLOW_SERVERLESS_URLS","InferenceHTTPClient","apiKey","serverUrl","__publicField","offer","workflowSpec","workspaceName","workflowId","config","hasWorkflowSpec","hasWorkspaceIdentifier","imageInputName","streamOutputNames","dataOutputNames","threadPoolWorkers","workflowsParameters","iceServers","processingTimeout","requestedPlan","requestedRegion","realtimeProcessing","rtspUrl","workflowConfiguration","payload","response","errorText","pipelineId","turnConfig","iceServersRaw","server","err","connectors","options","client","wrtcParams","proxyUrl","turnConfigUrl","useCamera","constraints","stream","t","stopStream","track","CHUNK_SIZE","BUFFER_LIMIT","POLL_INTERVAL","sleep","ms","resolve","FileUploader","file","channel","onProgress","totalBytes","chunkIndex","start","end","chunkBlob","chunkData","message","view","HEADER_SIZE","ChunkReassembler","frameId","totalChunks","frame","totalLength","sum","chunk","result","offset","i","parseBinaryHeader","buffer","waitForIceGathering","pc","timeoutMs","hasSrflx","candidateHandler","event","check","reject","setupRemoteStreamListener","DEFAULT_ICE_SERVERS","preparePeerConnection","localStream","customIceServers","hooks","sender","remoteStreamPromise","dataChannel","uploadChannel","modifiedOffer","disableInputStreamDownscaling","s","params","waitForChannelOpen","openHandler","errorHandler","timeout","RFWebRTCConnection","onData","messageEvent","completePayload","jsonString","data","error","baseUseStream","source","connector","onComplete","onFileUploadProgress","isRtsp","isFile","resolvedWrtcParams","answer","sdpAnswer","checkState","connection","useStream","useVideoFile","onUploadProgress","useRtspStream"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- (function(u,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(u=typeof globalThis<"u"?globalThis:u||self,p(u.RoboflowClient={}))})(this,function(u){"use strict";var te=Object.defineProperty;var re=(u,p,v)=>p in u?te(u,p,{enumerable:!0,configurable:!0,writable:!0,value:v}):u[p]=v;var d=(u,p,v)=>re(u,typeof p!="symbol"?p+"":p,v);var A;const p=typeof process<"u"&&((A=process.env)!=null&&A.RF_API_BASE_URL)?process.env.RF_API_BASE_URL:"https://api.roboflow.com",v=["https://serverless.roboflow.com"];class k{constructor(e,t="https://serverless.roboflow.com"){d(this,"apiKey");d(this,"serverUrl");this.apiKey=e,this.serverUrl=t}static init({apiKey:e,serverUrl:t}){if(!e)throw new Error("apiKey is required");return new k(e,t)}async initializeWebrtcWorker({offer:e,workflowSpec:t,workspaceName:a,workflowId:o,config:r={}}){if(!e||!e.sdp||!e.type)throw new Error("offer with sdp and type is required");const i=!!t,l=!!(a&&o);if(!i&&!l)throw new Error("Either workflowSpec OR (workspaceName + workflowId) is required");if(i&&l)throw new Error("Provide either workflowSpec OR (workspaceName + workflowId), not both");const{imageInputName:c="image",streamOutputNames:s=[],dataOutputNames:h=["string"],threadPoolWorkers:f=4,workflowsParameters:C={},iceServers:y,processingTimeout:T,requestedPlan:S,requestedRegion:R,realtimeProcessing:g=!0}=r,m={type:"WorkflowConfiguration",image_input_name:c,workflows_parameters:C,workflows_thread_pool_workers:f,cancel_thread_pool_tasks_on_exit:!0,video_metadata_input_name:"video_metadata"};i?m.workflow_specification=t:(m.workspace_name=a,m.workflow_id=o);const _={workflow_configuration:m,api_key:this.apiKey,webrtc_realtime_processing:g,webrtc_offer:{sdp:e.sdp,type:e.type},webrtc_config:y?{iceServers:y}:null,stream_output:s,data_output:h};T!==void 0&&(_.processing_timeout=T),S!==void 0&&(_.requested_plan=S),R!==void 0&&(_.requested_region=R);const b=await fetch(`${this.serverUrl}/initialise_webrtc_worker`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(_)});if(!b.ok){const F=await b.text().catch(()=>"");throw new Error(`initialise_webrtc_worker failed (${b.status}): ${F}`)}return await b.json()}async terminatePipeline({pipelineId:e}){if(!e)throw new Error("pipelineId is required");await fetch(`${this.serverUrl}/inference_pipelines/${e}/terminate?api_key=${this.apiKey}`,{method:"POST",headers:{"Content-Type":"application/json"}})}async fetchTurnConfig(){if(!v.includes(this.serverUrl))return null;try{const e=await fetch(`${p}/webrtc_turn_config?api_key=${this.apiKey}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!e.ok)return console.warn(`[RFWebRTC] Failed to fetch TURN config (${e.status}), using defaults`),null;const t=await e.json();let a;if(Array.isArray(t))a=t;else if(t.iceServers&&Array.isArray(t.iceServers))a=t.iceServers;else if(t.urls)a=[t];else return console.warn("[RFWebRTC] Invalid TURN config format, using defaults"),null;return a.map(r=>({urls:Array.isArray(r.urls)?r.urls:[r.urls],username:r.username,credential:r.credential}))}catch(e){return console.warn("[RFWebRTC] Error fetching TURN config:",e),null}}}const q={withApiKey(n,e={}){const{serverUrl:t}=e;typeof window<"u"&&console.warn("[Security Warning] Using API key directly in browser will expose it. Use connectors.withProxyUrl() for production. See: https://docs.roboflow.com/api-reference/authentication#securing-your-api-key");const a=k.init({apiKey:n,serverUrl:t});return{connectWrtc:async(o,r)=>(console.debug("wrtcParams",r),await a.initializeWebrtcWorker({offer:o,workflowSpec:r.workflowSpec,workspaceName:r.workspaceName,workflowId:r.workflowId,config:{imageInputName:r.imageInputName,streamOutputNames:r.streamOutputNames,dataOutputNames:r.dataOutputNames,threadPoolWorkers:r.threadPoolWorkers,workflowsParameters:r.workflowsParameters,iceServers:r.iceServers,processingTimeout:r.processingTimeout,requestedPlan:r.requestedPlan,requestedRegion:r.requestedRegion,realtimeProcessing:r.realtimeProcessing}})),getIceServers:async()=>await a.fetchTurnConfig(),_apiKey:n,_serverUrl:t}},withProxyUrl(n,e={}){const{turnConfigUrl:t}=e;return{connectWrtc:async(a,o)=>{const r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({offer:a,wrtcParams:o})});if(!r.ok){const i=await r.text().catch(()=>"");throw new Error(`Proxy request failed (${r.status}): ${i}`)}return await r.json()},getIceServers:t?async()=>{try{const a=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});return a.ok?(await a.json()).iceServers||null:(console.warn(`[RFWebRTC] Failed to fetch TURN config from proxy (${a.status})`),null)}catch(a){return console.warn("[RFWebRTC] Error fetching TURN config from proxy:",a),null}}:void 0}}};async function j(n={video:!0}){try{console.log("[RFStreams] requesting with",n);const e=await navigator.mediaDevices.getUserMedia(n);return console.log("[RFStreams] got stream",e.getVideoTracks().map(t=>({id:t.id,label:t.label}))),e}catch(e){console.warn("[RFStreams] failed, falling back",e);const t=await navigator.mediaDevices.getUserMedia({video:!0,audio:!1});return console.log("[RFStreams] fallback stream",t.getVideoTracks().map(a=>({id:a.id,label:a.label}))),t}}function W(n){n&&(n.getTracks().forEach(e=>e.stop()),console.log("[RFStreams] Stream stopped"))}const K=Object.freeze(Object.defineProperty({__proto__:null,stopStream:W,useCamera:j},Symbol.toStringTag,{value:"Module"})),U=49152,B=262144,V=10;function $(n){return new Promise(e=>setTimeout(e,n))}class I{constructor(e,t){d(this,"file");d(this,"channel");d(this,"totalChunks");d(this,"cancelled",!1);this.file=e,this.channel=t,this.totalChunks=Math.ceil(e.size/U)}cancel(){this.cancelled=!0}async upload(e){const t=this.file.size;for(let a=0;a<this.totalChunks;a++){if(this.cancelled)throw new Error("Upload cancelled");if(this.channel.readyState!=="open")throw new Error("Video upload interrupted");const o=a*U,r=Math.min(o+U,t),i=this.file.slice(o,r),l=new Uint8Array(await i.arrayBuffer()),c=new ArrayBuffer(8+l.length),s=new DataView(c);for(s.setUint32(0,a,!0),s.setUint32(4,this.totalChunks,!0),new Uint8Array(c,8).set(l);this.channel.bufferedAmount>B;){if(this.channel.readyState!=="open")throw new Error("Video upload interrupted");await $(V)}this.channel.send(c),e&&e(r,t)}}}const M=12;class L{constructor(){d(this,"pendingFrames",new Map)}processChunk(e,t,a,o){if(a===1)return o;this.pendingFrames.has(e)||this.pendingFrames.set(e,{chunks:new Map,totalChunks:a});const r=this.pendingFrames.get(e);if(r.chunks.set(t,o),r.chunks.size===a){const i=Array.from(r.chunks.values()).reduce((s,h)=>s+h.length,0),l=new Uint8Array(i);let c=0;for(let s=0;s<a;s++){const h=r.chunks.get(s);l.set(h,c),c+=h.length}return this.pendingFrames.delete(e),l}return null}clear(){this.pendingFrames.clear()}}function O(n){const e=new DataView(n),t=e.getUint32(0,!0),a=e.getUint32(4,!0),o=e.getUint32(8,!0),r=new Uint8Array(n,M);return{frameId:t,chunkIndex:a,totalChunks:o,payload:r}}async function H(n,e=6e3){if(n.iceGatheringState==="complete")return;let t=!1;const a=o=>{o.candidate&&o.candidate.type==="srflx"&&(t=!0)};n.addEventListener("icecandidate",a);try{await Promise.race([new Promise(o=>{const r=()=>{n.iceGatheringState==="complete"&&(n.removeEventListener("icegatheringstatechange",r),o())};n.addEventListener("icegatheringstatechange",r)}),new Promise((o,r)=>{setTimeout(()=>{t?o():(console.error("[ICE] timeout with NO srflx candidate! Connection may fail."),r(new Error("ICE gathering timeout without srflx candidate")))},e)})])}finally{n.removeEventListener("icecandidate",a)}}function z(n){return new Promise(e=>{n.addEventListener("track",t=>{t.streams&&t.streams[0]&&e(t.streams[0])})})}const G=[{urls:["stun:stun.l.google.com:19302"]}];async function J(n,e,t){if(!n&&!e||n&&e)throw new Error("Either localStream or file must be provided, but not both");const a=t??G,o=new RTCPeerConnection({iceServers:a});try{o.addTransceiver("video",{direction:"recvonly"})}catch(s){console.warn("[RFWebRTC] Could not add transceiver:",s)}n&&n.getVideoTracks().forEach(s=>{try{s.contentHint="detail"}catch{}o.addTrack(s,n)});const r=z(o),i=o.createDataChannel("inference",{ordered:!0});let l;e&&(l=o.createDataChannel("video_upload"));const c=await o.createOffer();return await o.setLocalDescription(c),await H(o),{pc:o,offer:o.localDescription,remoteStreamPromise:r,dataChannel:i,uploadChannel:l}}async function Z(n){const e=n.getSenders().find(a=>a.track&&a.track.kind==="video");if(!e)return;const t=e.getParameters();t.encodings=t.encodings||[{}],t.encodings[0].scaleResolutionDownBy=1;try{await e.setParameters(t)}catch(a){console.warn("[RFWebRTC] Failed to set encoding parameters:",a)}}function Q(n,e=3e4){return new Promise((t,a)=>{if(n.readyState==="open"){t();return}const o=()=>{n.removeEventListener("open",o),n.removeEventListener("error",r),clearTimeout(i),t()},r=()=>{n.removeEventListener("open",o),n.removeEventListener("error",r),clearTimeout(i),a(new Error("Datachannel error"))},i=setTimeout(()=>{n.removeEventListener("open",o),n.removeEventListener("error",r),a(new Error("Datachannel open timeout"))},e);n.addEventListener("open",o),n.addEventListener("error",r)})}class D{constructor(e,t,a,o,r,i){d(this,"pc");d(this,"_localStream");d(this,"remoteStreamPromise");d(this,"pipelineId");d(this,"apiKey");d(this,"dataChannel");d(this,"reassembler");d(this,"uploadChannel");d(this,"uploader");d(this,"onComplete");this.pc=e,this._localStream=i==null?void 0:i.localStream,this.remoteStreamPromise=t,this.pipelineId=a,this.apiKey=o,this.dataChannel=r,this.reassembler=new L,this.uploadChannel=i==null?void 0:i.uploadChannel,this.onComplete=i==null?void 0:i.onComplete,this.dataChannel.binaryType="arraybuffer";const l=i==null?void 0:i.onData;l&&(this.dataChannel.addEventListener("message",c=>{try{if(c.data instanceof ArrayBuffer){const{frameId:s,chunkIndex:h,totalChunks:f,payload:C}=O(c.data),y=this.reassembler.processChunk(s,h,f,C);if(y){const S=new TextDecoder("utf-8").decode(y),R=JSON.parse(S);l(R)}}else{const s=JSON.parse(c.data);l(s)}}catch(s){console.error("[RFWebRTC] Failed to parse data channel message:",s)}}),this.dataChannel.addEventListener("error",c=>{console.error("[RFWebRTC] Data channel error:",c)})),this.dataChannel.addEventListener("close",()=>{this.reassembler.clear(),this.onComplete&&this.onComplete()})}async remoteStream(){return await this.remoteStreamPromise}localStream(){return this._localStream}async cleanup(){if(this.uploader&&this.uploader.cancel(),this.reassembler.clear(),this.pipelineId&&this.apiKey)try{await k.init({apiKey:this.apiKey}).terminatePipeline({pipelineId:this.pipelineId})}catch(e){console.warn("[RFWebRTC] Failed to terminate pipeline:",e)}this.pc&&this.pc.connectionState!=="closed"&&this.pc.close(),this._localStream&&W(this._localStream)}async startUpload(e,t){if(!this.uploadChannel)throw new Error("No upload channel available. This connection was not created for file uploads.");await Q(this.uploadChannel),this.uploader=new I(e,this.uploadChannel),await this.uploader.upload(t)}cancelUpload(){this.uploader&&this.uploader.cancel()}reconfigureOutputs(e){const t={};e.streamOutput!==void 0&&(t.stream_output=e.streamOutput),e.dataOutput!==void 0&&(t.data_output=e.dataOutput),this.sendData(t)}sendData(e){if(this.dataChannel.readyState!=="open"){console.warn("[RFWebRTC] Data channel is not open. Current state:",this.dataChannel.readyState);return}try{const t=typeof e=="string"?e:JSON.stringify(e);this.dataChannel.send(t)}catch(t){console.error("[RFWebRTC] Failed to send data:",t)}}}async function N({source:n,connector:e,wrtcParams:t,onData:a,onComplete:o,onFileUploadProgress:r,options:i={}}){var F;if(!e||typeof e.connectWrtc!="function")throw new Error("connector must have a connectWrtc method");const l=n instanceof File,c=l?void 0:n,s=l?n:void 0;let h=t.iceServers;if((!h||h.length===0)&&e.getIceServers)try{const w=await e.getIceServers();w&&w.length>0&&(h=w,console.log("[RFWebRTC] Using TURN servers from connector"))}catch(w){console.warn("[RFWebRTC] Failed to fetch TURN config, using defaults:",w)}const{pc:f,offer:C,remoteStreamPromise:y,dataChannel:T,uploadChannel:S}=await J(c,s,h),R={...t,iceServers:h,realtimeProcessing:t.realtimeProcessing??!l},g=await e.connectWrtc({sdp:C.sdp,type:C.type},R),m={sdp:g.sdp,type:g.type};if(!(m!=null&&m.sdp)||!(m!=null&&m.type))throw console.error("[RFWebRTC] Invalid answer from server:",g),new Error("connector.connectWrtc must return answer with sdp and type");const _=((F=g==null?void 0:g.context)==null?void 0:F.pipeline_id)||null;await f.setRemoteDescription(m),await new Promise((w,x)=>{const E=()=>{f.connectionState==="connected"?(f.removeEventListener("connectionstatechange",E),w()):f.connectionState==="failed"&&(f.removeEventListener("connectionstatechange",E),x(new Error("WebRTC connection failed")))};f.addEventListener("connectionstatechange",E),E(),setTimeout(()=>{f.removeEventListener("connectionstatechange",E),x(new Error("WebRTC connection timeout after 30s"))},3e4)}),c&&i.disableInputStreamDownscaling!==!1&&await Z(f);const b=e._apiKey||null,P=new D(f,y,_,b,T,{localStream:c,uploadChannel:S,onData:a,onComplete:o});return s&&S&&P.startUpload(s,r).catch(w=>{console.error("[RFWebRTC] Upload error:",w)}),P}async function X({source:n,connector:e,wrtcParams:t,onData:a,options:o={}}){if(n instanceof File)throw new Error("useStream requires a MediaStream. Use useVideoFile for File uploads.");return N({source:n,connector:e,wrtcParams:t,onData:a,options:o})}async function Y({file:n,connector:e,wrtcParams:t,onData:a,onUploadProgress:o,onComplete:r}){return N({source:n,connector:e,wrtcParams:{...t,realtimeProcessing:t.realtimeProcessing??!0},onData:a,onComplete:r,onFileUploadProgress:o})}const ee=Object.freeze(Object.defineProperty({__proto__:null,ChunkReassembler:L,FileUploader:I,RFWebRTCConnection:D,parseBinaryHeader:O,useStream:X,useVideoFile:Y},Symbol.toStringTag,{value:"Module"}));u.InferenceHTTPClient=k,u.connectors=q,u.streams=K,u.webrtc=ee,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})});
1
+ (function(f,w){typeof exports=="object"&&typeof module<"u"?w(exports):typeof define=="function"&&define.amd?define(["exports"],w):(f=typeof globalThis<"u"?globalThis:f||self,w(f.RoboflowClient={}))})(this,function(f){"use strict";var oe=Object.defineProperty;var ie=(f,w,_)=>w in f?oe(f,w,{enumerable:!0,configurable:!0,writable:!0,value:_}):f[w]=_;var l=(f,w,_)=>ie(f,typeof w!="symbol"?w+"":w,_);var A;const w=typeof process<"u"&&((A=process.env)!=null&&A.RF_API_BASE_URL)?process.env.RF_API_BASE_URL:"https://api.roboflow.com",_=["https://serverless.roboflow.com"];class b{constructor(e,t="https://serverless.roboflow.com"){l(this,"apiKey");l(this,"serverUrl");this.apiKey=e,this.serverUrl=t}static init({apiKey:e,serverUrl:t}){if(!e)throw new Error("apiKey is required");return new b(e,t)}async initializeWebrtcWorker({offer:e,workflowSpec:t,workspaceName:n,workflowId:o,config:a={}}){if(!e||!e.sdp||!e.type)throw new Error("offer with sdp and type is required");const i=!!t,p=!!(n&&o);if(!i&&!p)throw new Error("Either workflowSpec OR (workspaceName + workflowId) is required");if(i&&p)throw new Error("Provide either workflowSpec OR (workspaceName + workflowId), not both");const{imageInputName:d="image",streamOutputNames:c=[],dataOutputNames:s=[],threadPoolWorkers:C=4,workflowsParameters:v={},iceServers:m,processingTimeout:u,requestedPlan:h,requestedRegion:R,realtimeProcessing:P=!0,rtspUrl:E}=a,T={type:"WorkflowConfiguration",image_input_name:d,workflows_parameters:v,workflows_thread_pool_workers:C,cancel_thread_pool_tasks_on_exit:!0,video_metadata_input_name:"video_metadata"};i?T.workflow_specification=t:(T.workspace_name=n,T.workflow_id=o);const g={workflow_configuration:T,api_key:this.apiKey,webrtc_realtime_processing:P,webrtc_offer:{sdp:e.sdp,type:e.type},webrtc_config:m?{iceServers:m}:null,stream_output:c,data_output:s};u!==void 0&&(g.processing_timeout=u),h!==void 0&&(g.requested_plan=h),R!==void 0&&(g.requested_region=R),E&&(g.rtsp_url=E);const y=await fetch(`${this.serverUrl}/initialise_webrtc_worker`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)});if(!y.ok){const W=await y.text().catch(()=>"");throw new Error(`initialise_webrtc_worker failed (${y.status}): ${W}`)}return await y.json()}async terminatePipeline({pipelineId:e}){if(!e)throw new Error("pipelineId is required");await fetch(`${this.serverUrl}/inference_pipelines/${e}/terminate?api_key=${this.apiKey}`,{method:"POST",headers:{"Content-Type":"application/json"}})}async fetchTurnConfig(){if(!_.includes(this.serverUrl))return null;try{const e=await fetch(`${w}/webrtc_turn_config?api_key=${this.apiKey}`,{method:"GET",headers:{"Content-Type":"application/json"}});if(!e.ok)return console.warn(`[RFWebRTC] Failed to fetch TURN config (${e.status}), using defaults`),null;const t=await e.json();let n;if(Array.isArray(t))n=t;else if(t.iceServers&&Array.isArray(t.iceServers))n=t.iceServers;else if(t.urls)n=[t];else return console.warn("[RFWebRTC] Invalid TURN config format, using defaults"),null;return n.map(a=>({urls:Array.isArray(a.urls)?a.urls:[a.urls],username:a.username,credential:a.credential}))}catch(e){return console.warn("[RFWebRTC] Error fetching TURN config:",e),null}}}const B={withApiKey(r,e={}){const{serverUrl:t}=e;typeof window<"u"&&console.warn("[Security Warning] Using API key directly in browser will expose it. Use connectors.withProxyUrl() for production. See: https://docs.roboflow.com/api-reference/authentication#securing-your-api-key");const n=b.init({apiKey:r,serverUrl:t});return{connectWrtc:async(o,a)=>(console.debug("wrtcParams",a),await n.initializeWebrtcWorker({offer:o,workflowSpec:a.workflowSpec,workspaceName:a.workspaceName,workflowId:a.workflowId,config:{imageInputName:a.imageInputName,streamOutputNames:a.streamOutputNames,dataOutputNames:a.dataOutputNames,threadPoolWorkers:a.threadPoolWorkers,workflowsParameters:a.workflowsParameters,iceServers:a.iceServers,processingTimeout:a.processingTimeout,requestedPlan:a.requestedPlan,requestedRegion:a.requestedRegion,realtimeProcessing:a.realtimeProcessing,rtspUrl:a.rtspUrl}})),getIceServers:async()=>await n.fetchTurnConfig(),_apiKey:r,_serverUrl:t}},withProxyUrl(r,e={}){const{turnConfigUrl:t}=e;return{connectWrtc:async(n,o)=>{const a=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({offer:n,wrtcParams:o})});if(!a.ok){const i=await a.text().catch(()=>"");throw new Error(`Proxy request failed (${a.status}): ${i}`)}return await a.json()},getIceServers:t?async()=>{try{const n=await fetch(t,{method:"GET",headers:{"Content-Type":"application/json"}});return n.ok?(await n.json()).iceServers||null:(console.warn(`[RFWebRTC] Failed to fetch TURN config from proxy (${n.status})`),null)}catch(n){return console.warn("[RFWebRTC] Error fetching TURN config from proxy:",n),null}}:void 0}}};async function V(r={video:!0}){try{console.log("[RFStreams] requesting with",r);const e=await navigator.mediaDevices.getUserMedia(r);return console.log("[RFStreams] got stream",e.getVideoTracks().map(t=>({id:t.id,label:t.label}))),e}catch(e){console.warn("[RFStreams] failed, falling back",e);const t=await navigator.mediaDevices.getUserMedia({video:!0,audio:!1});return console.log("[RFStreams] fallback stream",t.getVideoTracks().map(n=>({id:n.id,label:n.label}))),t}}function I(r){r&&(r.getTracks().forEach(e=>e.stop()),console.log("[RFStreams] Stream stopped"))}const $=Object.freeze(Object.defineProperty({__proto__:null,stopStream:I,useCamera:V},Symbol.toStringTag,{value:"Module"})),F=49152,M=262144,z=10;function H(r){return new Promise(e=>setTimeout(e,r))}class L{constructor(e,t){l(this,"file");l(this,"channel");l(this,"totalChunks");l(this,"cancelled",!1);this.file=e,this.channel=t,this.totalChunks=Math.ceil(e.size/F)}cancel(){this.cancelled=!0}async upload(e){const t=this.file.size;for(let n=0;n<this.totalChunks;n++){if(this.cancelled)throw new Error("Upload cancelled");if(this.channel.readyState!=="open")throw new Error("Video upload interrupted");const o=n*F,a=Math.min(o+F,t),i=this.file.slice(o,a),p=new Uint8Array(await i.arrayBuffer()),d=new ArrayBuffer(8+p.length),c=new DataView(d);for(c.setUint32(0,n,!0),c.setUint32(4,this.totalChunks,!0),new Uint8Array(d,8).set(p);this.channel.bufferedAmount>M;){if(this.channel.readyState!=="open")throw new Error("Video upload interrupted");await H(z)}this.channel.send(d),e&&e(a,t)}}}const G=12;class O{constructor(){l(this,"pendingFrames",new Map)}processChunk(e,t,n,o){if(n===1)return o;this.pendingFrames.has(e)||this.pendingFrames.set(e,{chunks:new Map,totalChunks:n});const a=this.pendingFrames.get(e);if(a.chunks.set(t,o),a.chunks.size===n){const i=Array.from(a.chunks.values()).reduce((c,s)=>c+s.length,0),p=new Uint8Array(i);let d=0;for(let c=0;c<n;c++){const s=a.chunks.get(c);p.set(s,d),d+=s.length}return this.pendingFrames.delete(e),p}return null}clear(){this.pendingFrames.clear()}}function D(r){const e=new DataView(r),t=e.getUint32(0,!0),n=e.getUint32(4,!0),o=e.getUint32(8,!0),a=new Uint8Array(r,G);return{frameId:t,chunkIndex:n,totalChunks:o,payload:a}}async function J(r,e=6e3){if(r.iceGatheringState==="complete")return;let t=!1;const n=o=>{o.candidate&&o.candidate.type==="srflx"&&(t=!0)};r.addEventListener("icecandidate",n);try{await Promise.race([new Promise(o=>{const a=()=>{r.iceGatheringState==="complete"&&(r.removeEventListener("icegatheringstatechange",a),o())};r.addEventListener("icegatheringstatechange",a)}),new Promise((o,a)=>{setTimeout(()=>{t?o():(console.error("[ICE] timeout with NO srflx candidate! Connection may fail."),a(new Error("ICE gathering timeout without srflx candidate")))},e)})])}finally{r.removeEventListener("icecandidate",n)}}function Z(r){return new Promise(e=>{r.addEventListener("track",t=>{t.streams&&t.streams[0]&&e(t.streams[0])})})}const Q=[{urls:["stun:stun.l.google.com:19302"]}];async function X(r,e,t,n,o){if([!!r,!!e,!!n].filter(Boolean).length!==1)throw new Error("Exactly one of localStream, file, or rtspUrl must be provided");const c=t??Q,s=new RTCPeerConnection({iceServers:c});o!=null&&o.onPeerConnectionCreated&&await o.onPeerConnectionCreated(s);try{s.addTransceiver("video",{direction:"recvonly"})}catch(h){console.warn("[RFWebRTC] Could not add transceiver:",h)}if(r)for(const h of r.getVideoTracks()){const R=s.addTrack(h,r);o!=null&&o.onTrackAdded&&await o.onTrackAdded(h,R,s)}const C=Z(s),v=s.createDataChannel("inference",{ordered:!0});let m;e&&(m=s.createDataChannel("video_upload"));let u=await s.createOffer();if(o!=null&&o.onOfferCreated){const h=await o.onOfferCreated(u);h&&(u=h)}return await s.setLocalDescription(u),await J(s),{pc:s,offer:s.localDescription,remoteStreamPromise:C,dataChannel:v,uploadChannel:m}}async function Y(r){const e=r.getSenders().find(n=>n.track&&n.track.kind==="video");if(!e)return;const t=e.getParameters();t.encodings=t.encodings||[{}],t.encodings[0].scaleResolutionDownBy=1;try{await e.setParameters(t)}catch(n){console.warn("[RFWebRTC] Failed to set encoding parameters:",n)}}function ee(r,e=3e4){return new Promise((t,n)=>{if(r.readyState==="open"){t();return}const o=()=>{r.removeEventListener("open",o),r.removeEventListener("error",a),clearTimeout(i),t()},a=()=>{r.removeEventListener("open",o),r.removeEventListener("error",a),clearTimeout(i),n(new Error("Datachannel error"))},i=setTimeout(()=>{r.removeEventListener("open",o),r.removeEventListener("error",a),n(new Error("Datachannel open timeout"))},e);r.addEventListener("open",o),r.addEventListener("error",a)})}class N{constructor(e,t,n,o,a,i){l(this,"peerConnection");l(this,"_localStream");l(this,"remoteStreamPromise");l(this,"pipelineId");l(this,"apiKey");l(this,"dataChannel");l(this,"reassembler");l(this,"uploadChannel");l(this,"uploader");l(this,"onComplete");this.peerConnection=e,this._localStream=i==null?void 0:i.localStream,this.remoteStreamPromise=t,this.pipelineId=n,this.apiKey=o,this.dataChannel=a,this.reassembler=new O,this.uploadChannel=i==null?void 0:i.uploadChannel,this.onComplete=i==null?void 0:i.onComplete,this.dataChannel.binaryType="arraybuffer";const p=i==null?void 0:i.onData;p&&(this.dataChannel.addEventListener("message",d=>{try{if(d.data instanceof ArrayBuffer){const{frameId:c,chunkIndex:s,totalChunks:C,payload:v}=D(d.data),m=this.reassembler.processChunk(c,s,C,v);if(m){const h=new TextDecoder("utf-8").decode(m),R=JSON.parse(h);p(R)}}else{const c=JSON.parse(d.data);p(c)}}catch(c){console.error("[RFWebRTC] Failed to parse data channel message:",c)}}),this.dataChannel.addEventListener("error",d=>{console.error("[RFWebRTC] Data channel error:",d)})),this.dataChannel.addEventListener("close",()=>{this.reassembler.clear(),this.onComplete&&this.onComplete()})}async remoteStream(){return await this.remoteStreamPromise}localStream(){return this._localStream}async cleanup(){if(this.uploader&&this.uploader.cancel(),this.reassembler.clear(),this.pipelineId&&this.apiKey)try{await b.init({apiKey:this.apiKey}).terminatePipeline({pipelineId:this.pipelineId})}catch(e){console.warn("[RFWebRTC] Failed to terminate pipeline:",e)}this.peerConnection&&this.peerConnection.connectionState!=="closed"&&this.peerConnection.close(),this._localStream&&I(this._localStream)}async startUpload(e,t){if(!this.uploadChannel)throw new Error("No upload channel available. This connection was not created for file uploads.");await ee(this.uploadChannel),this.uploader=new L(e,this.uploadChannel),await this.uploader.upload(t)}cancelUpload(){this.uploader&&this.uploader.cancel()}reconfigureOutputs(e){const t={};e.streamOutput!==void 0&&(t.stream_output=e.streamOutput),e.dataOutput!==void 0&&(t.data_output=e.dataOutput),this.sendData(t)}sendData(e){if(this.dataChannel.readyState!=="open"){console.warn("[RFWebRTC] Data channel is not open. Current state:",this.dataChannel.readyState);return}try{const t=typeof e=="string"?e:JSON.stringify(e);this.dataChannel.send(t)}catch(t){console.error("[RFWebRTC] Failed to send data:",t)}}}async function U({source:r,rtspUrl:e,connector:t,wrtcParams:n,onData:o,onComplete:a,onFileUploadProgress:i,options:p={},hooks:d}){var j;if(!t||typeof t.connectWrtc!="function")throw new Error("connector must have a connectWrtc method");const c=!!e,s=!c&&r instanceof File,C=!c&&!s&&r?r:void 0,v=s?r:void 0;let m=n.iceServers;if((!m||m.length===0)&&t.getIceServers)try{const S=await t.getIceServers();S&&S.length>0&&(m=S,console.log("[RFWebRTC] Using TURN servers from connector"))}catch(S){console.warn("[RFWebRTC] Failed to fetch TURN config, using defaults:",S)}const{pc:u,offer:h,remoteStreamPromise:R,dataChannel:P,uploadChannel:E}=await X(C,v,m,e,d),T={...n,iceServers:m,realtimeProcessing:n.realtimeProcessing??!s,rtspUrl:e},g=await t.connectWrtc({sdp:h.sdp,type:h.type},T),y={sdp:g.sdp,type:g.type};if(!(y!=null&&y.sdp)||!(y!=null&&y.type))throw console.error("[RFWebRTC] Invalid answer from server:",g),new Error("connector.connectWrtc must return answer with sdp and type");const x=((j=g==null?void 0:g.context)==null?void 0:j.pipeline_id)||null;await u.setRemoteDescription(y),await new Promise((S,K)=>{const k=()=>{u.connectionState==="connected"?(u.removeEventListener("connectionstatechange",k),S()):u.connectionState==="failed"&&(u.removeEventListener("connectionstatechange",k),K(new Error("WebRTC connection failed")))};u.addEventListener("connectionstatechange",k),k(),setTimeout(()=>{u.removeEventListener("connectionstatechange",k),K(new Error("WebRTC connection timeout after 30s"))},3e4)}),C&&p.disableInputStreamDownscaling!==!1&&await Y(u);const W=t._apiKey||null,q=new N(u,R,x,W,P,{localStream:C,uploadChannel:E,onData:o,onComplete:a});return v&&E&&q.startUpload(v,i).catch(S=>{console.error("[RFWebRTC] Upload error:",S)}),q}async function te({source:r,connector:e,wrtcParams:t,onData:n,options:o={},hooks:a}){if(r instanceof File)throw new Error("useStream requires a MediaStream. Use useVideoFile for File uploads.");return U({source:r,connector:e,wrtcParams:t,onData:n,options:o,hooks:a})}async function re({file:r,connector:e,wrtcParams:t,onData:n,onUploadProgress:o,onComplete:a,hooks:i}){return U({source:r,connector:e,wrtcParams:{...t,realtimeProcessing:t.realtimeProcessing??!0},onData:n,onComplete:a,onFileUploadProgress:o,hooks:i})}async function ne({rtspUrl:r,connector:e,wrtcParams:t,onData:n,hooks:o}){if(!r.startsWith("rtsp://")&&!r.startsWith("rtsps://"))throw new Error("Invalid RTSP URL: must start with rtsp:// or rtsps://");return U({rtspUrl:r,connector:e,wrtcParams:t,onData:n,hooks:o})}const ae=Object.freeze(Object.defineProperty({__proto__:null,ChunkReassembler:O,FileUploader:L,RFWebRTCConnection:N,parseBinaryHeader:D,useRtspStream:ne,useStream:te,useVideoFile:re},Symbol.toStringTag,{value:"Module"}));f.InferenceHTTPClient=b,f.connectors=B,f.streams=$,f.webrtc=ae,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})});
2
2
  //# sourceMappingURL=index.js.map