@roboflow/inference-sdk 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"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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"}
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- (function(l,s){typeof exports=="object"&&typeof module<"u"?s(exports):typeof define=="function"&&define.amd?define(["exports"],s):(l=typeof globalThis<"u"?globalThis:l||self,s(l.RoboflowClient={}))})(this,function(l){"use strict";var K=Object.defineProperty;var j=(l,s,y)=>s in l?K(l,s,{enumerable:!0,configurable:!0,writable:!0,value:y}):l[s]=y;var f=(l,s,y)=>j(l,typeof s!="symbol"?s+"":s,y);class s{constructor(e,t="https://serverless.roboflow.com"){f(this,"apiKey");f(this,"serverUrl");this.apiKey=e,this.serverUrl=t}static init({apiKey:e,serverUrl:t}){if(!e)throw new Error("apiKey is required");return new s(e,t)}async initializeWebrtcWorker({offer:e,workflowSpec:t,workspaceName:r,workflowId:n,config:a={}}){if(!e||!e.sdp||!e.type)throw new Error("offer with sdp and type is required");const i=!!t,c=!!(r&&n);if(!i&&!c)throw new Error("Either workflowSpec OR (workspaceName + workflowId) is required");if(i&&c)throw new Error("Provide either workflowSpec OR (workspaceName + workflowId), not both");const{imageInputName:p="image",streamOutputNames:d=[],dataOutputNames:u=["string"],threadPoolWorkers:h=4,workflowsParameters:_={},iceServers:S,processingTimeout:b,requestedPlan:v,requestedRegion:g}=a,k={type:"WorkflowConfiguration",image_input_name:p,workflows_parameters:_,workflows_thread_pool_workers:h,cancel_thread_pool_tasks_on_exit:!0,video_metadata_input_name:"video_metadata"};i?k.workflow_specification=t:(k.workspace_name=r,k.workflow_id=n);const w={workflow_configuration:k,api_key:this.apiKey,webrtc_realtime_processing:!0,webrtc_offer:{sdp:e.sdp,type:e.type},webrtc_config:S?{iceServers:S}:null,stream_output:d,data_output:u};b!==void 0&&(w.processing_timeout=b),v!==void 0&&(w.requested_plan=v),g!==void 0&&(w.requested_region=g),console.trace("payload",w);const m=await fetch(`${this.serverUrl}/initialise_webrtc_worker`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(w)});if(!m.ok){const q=await m.text().catch(()=>"");throw new Error(`initialise_webrtc_worker failed (${m.status}): ${q}`)}return await m.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"}})}}const y={withApiKey(o,e={}){const{serverUrl:t}=e;return 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"),{connectWrtc:async(r,n)=>{const a=s.init({apiKey:o,serverUrl:t});return console.log("wrtcParams",n),await a.initializeWebrtcWorker({offer:r,workflowSpec:n.workflowSpec,workspaceName:n.workspaceName,workflowId:n.workflowId,config:{imageInputName:n.imageInputName,streamOutputNames:n.streamOutputNames,dataOutputNames:n.dataOutputNames,threadPoolWorkers:n.threadPoolWorkers,workflowsParameters:n.workflowsParameters,iceServers:n.iceServers,processingTimeout:n.processingTimeout,requestedPlan:n.requestedPlan,requestedRegion:n.requestedRegion}})},_apiKey:o,_serverUrl:t}},withProxyUrl(o,e={}){return{connectWrtc:async(t,r)=>{const n=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({offer:t,wrtcParams:r})});if(!n.ok){const a=await n.text().catch(()=>"");throw new Error(`Proxy request failed (${n.status}): ${a}`)}return await n.json()}}}};async function O(o={video:!0}){try{console.log("[RFStreams] requesting with",o);const e=await navigator.mediaDevices.getUserMedia(o);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(r=>({id:r.id,label:r.label}))),t}}function C(o){o&&(o.getTracks().forEach(e=>e.stop()),console.log("[RFStreams] Stream stopped"))}const W=Object.freeze(Object.defineProperty({__proto__:null,stopStream:C,useCamera:O},Symbol.toStringTag,{value:"Module"})),P=12;class T{constructor(){f(this,"pendingFrames",new Map)}processChunk(e,t,r,n){if(r===1)return n;this.pendingFrames.has(e)||this.pendingFrames.set(e,{chunks:new Map,totalChunks:r});const a=this.pendingFrames.get(e);if(a.chunks.set(t,n),a.chunks.size===r){const i=Array.from(a.chunks.values()).reduce((d,u)=>d+u.length,0),c=new Uint8Array(i);let p=0;for(let d=0;d<r;d++){const u=a.chunks.get(d);c.set(u,p),p+=u.length}return this.pendingFrames.delete(e),c}return null}clear(){this.pendingFrames.clear()}}function R(o){const e=new DataView(o),t=e.getUint32(0,!0),r=e.getUint32(4,!0),n=e.getUint32(8,!0),a=new Uint8Array(o,P);return{frameId:t,chunkIndex:r,totalChunks:n,payload:a}}async function F(o,e=6e3){if(o.iceGatheringState==="complete")return;let t=!1;const r=n=>{n.candidate&&n.candidate.type==="srflx"&&(t=!0)};o.addEventListener("icecandidate",r);try{await Promise.race([new Promise(n=>{const a=()=>{o.iceGatheringState==="complete"&&(o.removeEventListener("icegatheringstatechange",a),n())};o.addEventListener("icegatheringstatechange",a)}),new Promise((n,a)=>{setTimeout(()=>{t?n():(console.error("[ICE] timeout with NO srflx candidate! Connection may fail."),a(new Error("ICE gathering timeout without srflx candidate")))},e)})])}finally{o.removeEventListener("icecandidate",r)}}function I(o){return new Promise(e=>{o.addEventListener("track",t=>{t.streams&&t.streams[0]&&e(t.streams[0])})})}const D=[{urls:["stun:stun.l.google.com:19302"]}];async function N(o,e){const t=e??D,r=new RTCPeerConnection({iceServers:t});try{r.addTransceiver("video",{direction:"recvonly"})}catch(c){console.warn("[RFWebRTC] Could not add transceiver:",c)}o.getVideoTracks().forEach(c=>{try{c.contentHint="detail"}catch{}r.addTrack(c,o)});const n=I(r),a=r.createDataChannel("roboflow-control",{ordered:!0}),i=await r.createOffer();return await r.setLocalDescription(i),await F(r),{pc:r,offer:r.localDescription,remoteStreamPromise:n,dataChannel:a}}async function U(o){const e=o.getSenders().find(r=>r.track&&r.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(r){console.warn("[RFWebRTC] Failed to set encoding parameters:",r)}}class E{constructor(e,t,r,n,a,i,c){f(this,"pc");f(this,"_localStream");f(this,"remoteStreamPromise");f(this,"pipelineId");f(this,"apiKey");f(this,"dataChannel");f(this,"reassembler");this.pc=e,this._localStream=t,this.remoteStreamPromise=r,this.pipelineId=n,this.apiKey=a,this.dataChannel=i,this.reassembler=new T,this.dataChannel.binaryType="arraybuffer",c&&(this.dataChannel.addEventListener("open",()=>{}),this.dataChannel.addEventListener("message",p=>{try{if(p.data instanceof ArrayBuffer){const{frameId:d,chunkIndex:u,totalChunks:h,payload:_}=R(p.data),S=this.reassembler.processChunk(d,u,h,_);if(S){const v=new TextDecoder("utf-8").decode(S),g=JSON.parse(v);c(g)}}else{const d=JSON.parse(p.data);c(d)}}catch(d){console.error("[RFWebRTC] Failed to parse data channel message:",d)}}),this.dataChannel.addEventListener("error",p=>{console.error("[RFWebRTC] Data channel error:",p)}),this.dataChannel.addEventListener("close",()=>{this.reassembler.clear()}))}async remoteStream(){return await this.remoteStreamPromise}localStream(){return this._localStream}async cleanup(){this.reassembler.clear(),this.pipelineId&&this.apiKey&&await s.init({apiKey:this.apiKey}).terminatePipeline({pipelineId:this.pipelineId}),this.pc&&this.pc.connectionState!=="closed"&&this.pc.close(),C(this._localStream)}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 L({source:o,connector:e,wrtcParams:t,onData:r,options:n={}}){var g;if(!e||typeof e.connectWrtc!="function")throw new Error("connector must have a connectWrtc method");const a=o,{pc:i,offer:c,remoteStreamPromise:p,dataChannel:d}=await N(a,t.iceServers),u=await e.connectWrtc({sdp:c.sdp,type:c.type},t),h={sdp:u.sdp,type:u.type};if(!(h!=null&&h.sdp)||!(h!=null&&h.type))throw console.error("[RFWebRTC] Invalid answer from server:",u),new Error("connector.connectWrtc must return answer with sdp and type");const _=((g=u==null?void 0:u.context)==null?void 0:g.pipeline_id)||null;await i.setRemoteDescription(h),await new Promise((k,w)=>{const m=()=>{i.connectionState==="connected"?(i.removeEventListener("connectionstatechange",m),k()):i.connectionState==="failed"&&(i.removeEventListener("connectionstatechange",m),w(new Error("WebRTC connection failed")))};i.addEventListener("connectionstatechange",m),m(),setTimeout(()=>{i.removeEventListener("connectionstatechange",m),w(new Error("WebRTC connection timeout after 30s"))},3e4)}),n.disableInputStreamDownscaling!==!1&&await U(i);const b=e._apiKey||null;return new E(i,a,p,_,b,d,r)}const x=Object.freeze(Object.defineProperty({__proto__:null,ChunkReassembler:T,RFWebRTCConnection:E,parseBinaryHeader:R,useStream:L},Symbol.toStringTag,{value:"Module"}));l.InferenceHTTPClient=s,l.connectors=y,l.streams=W,l.webrtc=x,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
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"})});
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.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":["RF_API_BASE_URL","_a","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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"}
@@ -26,6 +26,12 @@ export interface WebRTCWorkerConfig {
26
26
  * @example "us"
27
27
  */
28
28
  requestedRegion?: string;
29
+ /**
30
+ * Set to false for file upload mode (batch processing).
31
+ * When false, server processes all frames sequentially instead of dropping frames.
32
+ * @default true
33
+ */
34
+ realtimeProcessing?: boolean;
29
35
  }
30
36
  /**
31
37
  * ICE server configuration for WebRTC connections
@@ -96,9 +102,23 @@ export interface WebRTCParams {
96
102
  * @example "us"
97
103
  */
98
104
  requestedRegion?: string;
105
+ /**
106
+ * Set to false for file upload mode (batch processing).
107
+ * When false, server processes all frames sequentially instead of dropping frames.
108
+ * @default true
109
+ */
110
+ realtimeProcessing?: boolean;
99
111
  }
100
112
  export interface Connector {
101
113
  connectWrtc(offer: WebRTCOffer, wrtcParams: WebRTCParams): Promise<WebRTCWorkerResponse>;
114
+ /**
115
+ * Fetch ICE servers (TURN configuration) for WebRTC connections
116
+ * This should be called BEFORE creating the RTCPeerConnection to ensure
117
+ * proper NAT traversal configuration.
118
+ *
119
+ * @returns Promise resolving to ICE server configuration, or null/undefined if not available
120
+ */
121
+ getIceServers?(): Promise<RTCIceServerConfig[] | null>;
102
122
  _apiKey?: string;
103
123
  _serverUrl?: string;
104
124
  }
@@ -149,6 +169,23 @@ export declare class InferenceHTTPClient {
149
169
  terminatePipeline({ pipelineId }: {
150
170
  pipelineId: string;
151
171
  }): Promise<void>;
172
+ /**
173
+ * Fetch TURN server configuration from Roboflow API
174
+ *
175
+ * This automatically fetches TURN server credentials for improved WebRTC
176
+ * connectivity through firewalls and NAT. Only applicable when using
177
+ * Roboflow serverless infrastructure.
178
+ *
179
+ * @returns Promise resolving to ICE server configuration, or null if not applicable
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * const client = InferenceHTTPClient.init({ apiKey: "your-api-key" });
184
+ * const iceServers = await client.fetchTurnConfig();
185
+ * // Returns: [{ urls: ["turn:..."], username: "...", credential: "..." }]
186
+ * ```
187
+ */
188
+ fetchTurnConfig(): Promise<RTCIceServerConfig[] | null>;
152
189
  }
153
190
  /**
154
191
  * Connectors for establishing WebRTC connections to Roboflow
@@ -181,24 +218,41 @@ export declare const connectors: {
181
218
  * Your backend receives the offer and wrtcParams, adds the secret API key,
182
219
  * and forwards to Roboflow. This keeps your API key secure.
183
220
  *
184
- * @param proxyUrl - Backend proxy endpoint URL
185
- * @param options - Additional options (reserved for future use)
186
- * @returns Connector with connectWrtc method
221
+ * For improved WebRTC connectivity through firewalls, implement a separate
222
+ * endpoint for TURN server configuration that calls `fetchTurnConfig()`.
223
+ *
224
+ * @param proxyUrl - Backend proxy endpoint URL for WebRTC initialization
225
+ * @param options - Additional options
226
+ * @param options.turnConfigUrl - Optional URL for fetching TURN server configuration
227
+ * @returns Connector with connectWrtc and optional getIceServers methods
187
228
  *
188
229
  * @example
189
230
  * ```typescript
190
- * const connector = connectors.withProxyUrl('/api/init-webrtc');
191
- * const answer = await connector.connectWrtc(offer, wrtcParams);
231
+ * // Frontend: Create connector with TURN config endpoint
232
+ * const connector = connectors.withProxyUrl('/api/init-webrtc', {
233
+ * turnConfigUrl: '/api/turn-config'
234
+ * });
192
235
  * ```
193
236
  *
194
237
  * @example
195
- * Backend implementation (Express):
238
+ * Backend implementation (Express) with TURN server support:
196
239
  * ```typescript
240
+ * // Endpoint for TURN configuration (called first by SDK)
241
+ * app.get('/api/turn-config', async (req, res) => {
242
+ * const client = InferenceHTTPClient.init({
243
+ * apiKey: process.env.ROBOFLOW_API_KEY
244
+ * });
245
+ * const iceServers = await client.fetchTurnConfig();
246
+ * res.json({ iceServers });
247
+ * });
248
+ *
249
+ * // Endpoint for WebRTC initialization
197
250
  * app.post('/api/init-webrtc', async (req, res) => {
198
251
  * const { offer, wrtcParams } = req.body;
199
252
  * const client = InferenceHTTPClient.init({
200
253
  * apiKey: process.env.ROBOFLOW_API_KEY
201
254
  * });
255
+ *
202
256
  * const answer = await client.initializeWebrtcWorker({
203
257
  * offer,
204
258
  * workflowSpec: wrtcParams.workflowSpec,
@@ -216,10 +270,13 @@ export declare const connectors: {
216
270
  * requestedRegion: wrtcParams.requestedRegion
217
271
  * }
218
272
  * });
273
+ *
219
274
  * res.json(answer);
220
275
  * });
221
276
  * ```
222
277
  */
223
- withProxyUrl(proxyUrl: string, options?: Record<string, any>): Connector;
278
+ withProxyUrl(proxyUrl: string, options?: {
279
+ turnConfigUrl?: string;
280
+ }): Connector;
224
281
  };
225
282
  //# sourceMappingURL=inference-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inference-api.d.ts","sourceRoot":"","sources":["../src/inference-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"inference-api.d.ts","sourceRoot":"","sources":["../src/inference-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Video file upload via WebRTC datachannel
3
+ *
4
+ * This module provides the FileUploader class for chunked file uploads
5
+ * through WebRTC datachannels with backpressure handling.
6
+ */
7
+ /**
8
+ * FileUploader handles chunked file upload with backpressure
9
+ *
10
+ * Uploads files through a WebRTC datachannel in 49KB chunks with
11
+ * intelligent backpressure handling to prevent overwhelming the network.
12
+ */
13
+ export declare class FileUploader {
14
+ private file;
15
+ private channel;
16
+ private totalChunks;
17
+ private cancelled;
18
+ constructor(file: File, channel: RTCDataChannel);
19
+ /**
20
+ * Cancel the upload
21
+ */
22
+ cancel(): void;
23
+ /**
24
+ * Upload the file in chunks with backpressure handling
25
+ *
26
+ * @param onProgress - Optional callback for progress updates (bytesUploaded, totalBytes)
27
+ */
28
+ upload(onProgress?: (bytesUploaded: number, totalBytes: number) => void): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=video-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-upload.d.ts","sourceRoot":"","sources":["../src/video-upload.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH;;;;;GAKG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAkB;gBAEvB,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc;IAM/C;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;;;OAIG;IACG,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CA4C9F"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Shared types for WebRTC inference
3
+ *
4
+ * These types are used by both live streaming (useStream) and
5
+ * video file upload (useVideoFile) functionality.
6
+ */
7
+ /**
8
+ * Video metadata from WebRTC inference results
9
+ *
10
+ * Contains frame timing and identification information.
11
+ */
12
+ export interface WebRTCVideoMetadata {
13
+ /** Sequential frame identifier */
14
+ frame_id: number;
15
+ /** ISO 8601 timestamp when frame was received by server */
16
+ received_at: string;
17
+ /** Presentation timestamp from video container */
18
+ pts?: number | null;
19
+ /** Time base for pts conversion (pts * time_base = seconds) */
20
+ time_base?: number | null;
21
+ /** FPS declared in video metadata or by client */
22
+ declared_fps?: number | null;
23
+ /** Actual measured FPS during processing */
24
+ measured_fps?: number | null;
25
+ /** Signals end of video file processing */
26
+ processing_complete?: boolean;
27
+ }
28
+ /**
29
+ * Output data from WebRTC inference
30
+ *
31
+ * This is the structure of data received via the `onData` callback
32
+ * for both live streams and video file uploads.
33
+ */
34
+ export interface WebRTCOutputData {
35
+ /** Workflow output data (predictions, counts, etc.) */
36
+ serialized_output_data?: Record<string, any> | null;
37
+ /** Video frame metadata */
38
+ video_metadata?: WebRTCVideoMetadata | null;
39
+ /** List of error messages from processing */
40
+ errors: string[];
41
+ /** Signals end of video file processing */
42
+ processing_complete: boolean;
43
+ }
44
+ //# sourceMappingURL=webrtc-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webrtc-types.d.ts","sourceRoot":"","sources":["../src/webrtc-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,2CAA2C;IAC3C,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACpD,2BAA2B;IAC3B,cAAc,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,6CAA6C;IAC7C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,2CAA2C;IAC3C,mBAAmB,EAAE,OAAO,CAAC;CAC9B"}