@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.
package/dist/index.es.js CHANGED
@@ -1,20 +1,24 @@
1
- var T = Object.defineProperty;
2
- var O = (a, e, t) => e in a ? T(a, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[e] = t;
3
- var u = (a, e, t) => O(a, typeof e != "symbol" ? e + "" : e, t);
4
- class _ {
1
+ var N = Object.defineProperty;
2
+ var A = (r, e, t) => e in r ? N(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3
+ var d = (r, e, t) => A(r, typeof e != "symbol" ? e + "" : e, t);
4
+ var U;
5
+ const x = typeof process < "u" && ((U = process.env) != null && U.RF_API_BASE_URL) ? process.env.RF_API_BASE_URL : "https://api.roboflow.com", q = [
6
+ "https://serverless.roboflow.com"
7
+ ];
8
+ class b {
5
9
  /**
6
10
  * @private
7
11
  * Use InferenceHTTPClient.init() instead
8
12
  */
9
13
  constructor(e, t = "https://serverless.roboflow.com") {
10
- u(this, "apiKey");
11
- u(this, "serverUrl");
14
+ d(this, "apiKey");
15
+ d(this, "serverUrl");
12
16
  this.apiKey = e, this.serverUrl = t;
13
17
  }
14
18
  static init({ apiKey: e, serverUrl: t }) {
15
19
  if (!e)
16
20
  throw new Error("apiKey is required");
17
- return new _(e, t);
21
+ return new b(e, t);
18
22
  }
19
23
  /**
20
24
  * Initialize a WebRTC worker pipeline
@@ -44,59 +48,60 @@ class _ {
44
48
  async initializeWebrtcWorker({
45
49
  offer: e,
46
50
  workflowSpec: t,
47
- workspaceName: n,
48
- workflowId: r,
49
- config: o = {}
51
+ workspaceName: a,
52
+ workflowId: o,
53
+ config: n = {}
50
54
  }) {
51
55
  if (!e || !e.sdp || !e.type)
52
56
  throw new Error("offer with sdp and type is required");
53
- const i = !!t, s = !!(n && r);
54
- if (!i && !s)
57
+ const i = !!t, l = !!(a && o);
58
+ if (!i && !l)
55
59
  throw new Error("Either workflowSpec OR (workspaceName + workflowId) is required");
56
- if (i && s)
60
+ if (i && l)
57
61
  throw new Error("Provide either workflowSpec OR (workspaceName + workflowId), not both");
58
62
  const {
59
- imageInputName: d = "image",
60
- streamOutputNames: c = [],
61
- dataOutputNames: l = ["string"],
62
- threadPoolWorkers: p = 4,
63
+ imageInputName: c = "image",
64
+ streamOutputNames: s = [],
65
+ dataOutputNames: u = ["string"],
66
+ threadPoolWorkers: h = 4,
63
67
  workflowsParameters: y = {},
64
68
  iceServers: w,
65
- processingTimeout: k,
66
- requestedPlan: S,
67
- requestedRegion: f
68
- } = o, g = {
69
+ processingTimeout: R,
70
+ requestedPlan: g,
71
+ requestedRegion: v,
72
+ realtimeProcessing: m = !0
73
+ } = n, p = {
69
74
  type: "WorkflowConfiguration",
70
- image_input_name: d,
75
+ image_input_name: c,
71
76
  workflows_parameters: y,
72
- workflows_thread_pool_workers: p,
77
+ workflows_thread_pool_workers: h,
73
78
  cancel_thread_pool_tasks_on_exit: !0,
74
79
  video_metadata_input_name: "video_metadata"
75
80
  };
76
- i ? g.workflow_specification = t : (g.workspace_name = n, g.workflow_id = r);
77
- const m = {
78
- workflow_configuration: g,
81
+ i ? p.workflow_specification = t : (p.workspace_name = a, p.workflow_id = o);
82
+ const S = {
83
+ workflow_configuration: p,
79
84
  api_key: this.apiKey,
80
- webrtc_realtime_processing: !0,
85
+ webrtc_realtime_processing: m,
81
86
  webrtc_offer: {
82
87
  sdp: e.sdp,
83
88
  type: e.type
84
89
  },
85
90
  webrtc_config: w ? { iceServers: w } : null,
86
- stream_output: c,
87
- data_output: l
91
+ stream_output: s,
92
+ data_output: u
88
93
  };
89
- k !== void 0 && (m.processing_timeout = k), S !== void 0 && (m.requested_plan = S), f !== void 0 && (m.requested_region = f), console.trace("payload", m);
90
- const h = await fetch(`${this.serverUrl}/initialise_webrtc_worker`, {
94
+ R !== void 0 && (S.processing_timeout = R), g !== void 0 && (S.requested_plan = g), v !== void 0 && (S.requested_region = v);
95
+ const C = await fetch(`${this.serverUrl}/initialise_webrtc_worker`, {
91
96
  method: "POST",
92
97
  headers: { "Content-Type": "application/json" },
93
- body: JSON.stringify(m)
98
+ body: JSON.stringify(S)
94
99
  });
95
- if (!h.ok) {
96
- const E = await h.text().catch(() => "");
97
- throw new Error(`initialise_webrtc_worker failed (${h.status}): ${E}`);
100
+ if (!C.ok) {
101
+ const k = await C.text().catch(() => "");
102
+ throw new Error(`initialise_webrtc_worker failed (${C.status}): ${k}`);
98
103
  }
99
- return await h.json();
104
+ return await C.json();
100
105
  }
101
106
  async terminatePipeline({ pipelineId: e }) {
102
107
  if (!e)
@@ -109,8 +114,56 @@ class _ {
109
114
  }
110
115
  );
111
116
  }
117
+ /**
118
+ * Fetch TURN server configuration from Roboflow API
119
+ *
120
+ * This automatically fetches TURN server credentials for improved WebRTC
121
+ * connectivity through firewalls and NAT. Only applicable when using
122
+ * Roboflow serverless infrastructure.
123
+ *
124
+ * @returns Promise resolving to ICE server configuration, or null if not applicable
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const client = InferenceHTTPClient.init({ apiKey: "your-api-key" });
129
+ * const iceServers = await client.fetchTurnConfig();
130
+ * // Returns: [{ urls: ["turn:..."], username: "...", credential: "..." }]
131
+ * ```
132
+ */
133
+ async fetchTurnConfig() {
134
+ if (!q.includes(this.serverUrl))
135
+ return null;
136
+ try {
137
+ const e = await fetch(
138
+ `${x}/webrtc_turn_config?api_key=${this.apiKey}`,
139
+ {
140
+ method: "GET",
141
+ headers: { "Content-Type": "application/json" }
142
+ }
143
+ );
144
+ if (!e.ok)
145
+ return console.warn(`[RFWebRTC] Failed to fetch TURN config (${e.status}), using defaults`), null;
146
+ const t = await e.json();
147
+ let a;
148
+ if (Array.isArray(t))
149
+ a = t;
150
+ else if (t.iceServers && Array.isArray(t.iceServers))
151
+ a = t.iceServers;
152
+ else if (t.urls)
153
+ a = [t];
154
+ else
155
+ return console.warn("[RFWebRTC] Invalid TURN config format, using defaults"), null;
156
+ return a.map((n) => ({
157
+ urls: Array.isArray(n.urls) ? n.urls : [n.urls],
158
+ username: n.username,
159
+ credential: n.credential
160
+ }));
161
+ } catch (e) {
162
+ return console.warn("[RFWebRTC] Error fetching TURN config:", e), null;
163
+ }
164
+ }
112
165
  }
113
- const K = {
166
+ const ee = {
114
167
  /**
115
168
  * Create a connector that uses API key directly
116
169
  *
@@ -129,33 +182,37 @@ const K = {
129
182
  * const answer = await connector.connectWrtc(offer, wrtcParams);
130
183
  * ```
131
184
  */
132
- withApiKey(a, e = {}) {
185
+ withApiKey(r, e = {}) {
133
186
  const { serverUrl: t } = e;
134
- return typeof window < "u" && console.warn(
187
+ typeof window < "u" && console.warn(
135
188
  "[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"
136
- ), {
137
- connectWrtc: async (n, r) => {
138
- const o = _.init({ apiKey: a, serverUrl: t });
139
- return console.log("wrtcParams", r), await o.initializeWebrtcWorker({
140
- offer: n,
141
- workflowSpec: r.workflowSpec,
142
- workspaceName: r.workspaceName,
143
- workflowId: r.workflowId,
144
- config: {
145
- imageInputName: r.imageInputName,
146
- streamOutputNames: r.streamOutputNames,
147
- dataOutputNames: r.dataOutputNames,
148
- threadPoolWorkers: r.threadPoolWorkers,
149
- workflowsParameters: r.workflowsParameters,
150
- iceServers: r.iceServers,
151
- processingTimeout: r.processingTimeout,
152
- requestedPlan: r.requestedPlan,
153
- requestedRegion: r.requestedRegion
154
- }
155
- });
156
- },
189
+ );
190
+ const a = b.init({ apiKey: r, serverUrl: t });
191
+ return {
192
+ connectWrtc: async (o, n) => (console.debug("wrtcParams", n), await a.initializeWebrtcWorker({
193
+ offer: o,
194
+ workflowSpec: n.workflowSpec,
195
+ workspaceName: n.workspaceName,
196
+ workflowId: n.workflowId,
197
+ config: {
198
+ imageInputName: n.imageInputName,
199
+ streamOutputNames: n.streamOutputNames,
200
+ dataOutputNames: n.dataOutputNames,
201
+ threadPoolWorkers: n.threadPoolWorkers,
202
+ workflowsParameters: n.workflowsParameters,
203
+ iceServers: n.iceServers,
204
+ processingTimeout: n.processingTimeout,
205
+ requestedPlan: n.requestedPlan,
206
+ requestedRegion: n.requestedRegion,
207
+ realtimeProcessing: n.realtimeProcessing
208
+ }
209
+ })),
210
+ /**
211
+ * Fetch TURN server configuration for improved WebRTC connectivity
212
+ */
213
+ getIceServers: async () => await a.fetchTurnConfig(),
157
214
  // Store apiKey for cleanup
158
- _apiKey: a,
215
+ _apiKey: r,
159
216
  _serverUrl: t
160
217
  };
161
218
  },
@@ -165,24 +222,41 @@ const K = {
165
222
  * Your backend receives the offer and wrtcParams, adds the secret API key,
166
223
  * and forwards to Roboflow. This keeps your API key secure.
167
224
  *
168
- * @param proxyUrl - Backend proxy endpoint URL
169
- * @param options - Additional options (reserved for future use)
170
- * @returns Connector with connectWrtc method
225
+ * For improved WebRTC connectivity through firewalls, implement a separate
226
+ * endpoint for TURN server configuration that calls `fetchTurnConfig()`.
227
+ *
228
+ * @param proxyUrl - Backend proxy endpoint URL for WebRTC initialization
229
+ * @param options - Additional options
230
+ * @param options.turnConfigUrl - Optional URL for fetching TURN server configuration
231
+ * @returns Connector with connectWrtc and optional getIceServers methods
171
232
  *
172
233
  * @example
173
234
  * ```typescript
174
- * const connector = connectors.withProxyUrl('/api/init-webrtc');
175
- * const answer = await connector.connectWrtc(offer, wrtcParams);
235
+ * // Frontend: Create connector with TURN config endpoint
236
+ * const connector = connectors.withProxyUrl('/api/init-webrtc', {
237
+ * turnConfigUrl: '/api/turn-config'
238
+ * });
176
239
  * ```
177
240
  *
178
241
  * @example
179
- * Backend implementation (Express):
242
+ * Backend implementation (Express) with TURN server support:
180
243
  * ```typescript
244
+ * // Endpoint for TURN configuration (called first by SDK)
245
+ * app.get('/api/turn-config', async (req, res) => {
246
+ * const client = InferenceHTTPClient.init({
247
+ * apiKey: process.env.ROBOFLOW_API_KEY
248
+ * });
249
+ * const iceServers = await client.fetchTurnConfig();
250
+ * res.json({ iceServers });
251
+ * });
252
+ *
253
+ * // Endpoint for WebRTC initialization
181
254
  * app.post('/api/init-webrtc', async (req, res) => {
182
255
  * const { offer, wrtcParams } = req.body;
183
256
  * const client = InferenceHTTPClient.init({
184
257
  * apiKey: process.env.ROBOFLOW_API_KEY
185
258
  * });
259
+ *
186
260
  * const answer = await client.initializeWebrtcWorker({
187
261
  * offer,
188
262
  * workflowSpec: wrtcParams.workflowSpec,
@@ -200,72 +274,129 @@ const K = {
200
274
  * requestedRegion: wrtcParams.requestedRegion
201
275
  * }
202
276
  * });
277
+ *
203
278
  * res.json(answer);
204
279
  * });
205
280
  * ```
206
281
  */
207
- withProxyUrl(a, e = {}) {
282
+ withProxyUrl(r, e = {}) {
283
+ const { turnConfigUrl: t } = e;
208
284
  return {
209
- connectWrtc: async (t, n) => {
210
- const r = await fetch(a, {
285
+ connectWrtc: async (a, o) => {
286
+ const n = await fetch(r, {
211
287
  method: "POST",
212
288
  headers: { "Content-Type": "application/json" },
213
289
  body: JSON.stringify({
214
- offer: t,
215
- wrtcParams: n
290
+ offer: a,
291
+ wrtcParams: o
216
292
  })
217
293
  });
218
- if (!r.ok) {
219
- const o = await r.text().catch(() => "");
220
- throw new Error(`Proxy request failed (${r.status}): ${o}`);
294
+ if (!n.ok) {
295
+ const i = await n.text().catch(() => "");
296
+ throw new Error(`Proxy request failed (${n.status}): ${i}`);
221
297
  }
222
- return await r.json();
223
- }
298
+ return await n.json();
299
+ },
300
+ /**
301
+ * Fetch TURN server configuration from the proxy backend
302
+ * Only available if turnConfigUrl was provided
303
+ */
304
+ getIceServers: t ? async () => {
305
+ try {
306
+ const a = await fetch(t, {
307
+ method: "GET",
308
+ headers: { "Content-Type": "application/json" }
309
+ });
310
+ return a.ok ? (await a.json()).iceServers || null : (console.warn(`[RFWebRTC] Failed to fetch TURN config from proxy (${a.status})`), null);
311
+ } catch (a) {
312
+ return console.warn("[RFWebRTC] Error fetching TURN config from proxy:", a), null;
313
+ }
314
+ } : void 0
224
315
  };
225
316
  }
226
317
  };
227
- async function W(a = { video: !0 }) {
318
+ async function K(r = { video: !0 }) {
228
319
  try {
229
- console.log("[RFStreams] requesting with", a);
230
- const e = await navigator.mediaDevices.getUserMedia(a);
320
+ console.log("[RFStreams] requesting with", r);
321
+ const e = await navigator.mediaDevices.getUserMedia(r);
231
322
  return console.log("[RFStreams] got stream", e.getVideoTracks().map((t) => ({ id: t.id, label: t.label }))), e;
232
323
  } catch (e) {
233
324
  console.warn("[RFStreams] failed, falling back", e);
234
325
  const t = await navigator.mediaDevices.getUserMedia({ video: !0, audio: !1 });
235
- return console.log("[RFStreams] fallback stream", t.getVideoTracks().map((n) => ({ id: n.id, label: n.label }))), t;
326
+ return console.log("[RFStreams] fallback stream", t.getVideoTracks().map((a) => ({ id: a.id, label: a.label }))), t;
236
327
  }
237
328
  }
238
- function v(a) {
239
- a && (a.getTracks().forEach((e) => e.stop()), console.log("[RFStreams] Stream stopped"));
329
+ function W(r) {
330
+ r && (r.getTracks().forEach((e) => e.stop()), console.log("[RFStreams] Stream stopped"));
240
331
  }
241
- const j = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
332
+ const te = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
242
333
  __proto__: null,
243
- stopStream: v,
244
- useCamera: W
245
- }, Symbol.toStringTag, { value: "Module" })), F = 12;
246
- class b {
334
+ stopStream: W,
335
+ useCamera: K
336
+ }, Symbol.toStringTag, { value: "Module" })), T = 49152, j = 262144, B = 10;
337
+ function V(r) {
338
+ return new Promise((e) => setTimeout(e, r));
339
+ }
340
+ class I {
341
+ constructor(e, t) {
342
+ d(this, "file");
343
+ d(this, "channel");
344
+ d(this, "totalChunks");
345
+ d(this, "cancelled", !1);
346
+ this.file = e, this.channel = t, this.totalChunks = Math.ceil(e.size / T);
347
+ }
348
+ /**
349
+ * Cancel the upload
350
+ */
351
+ cancel() {
352
+ this.cancelled = !0;
353
+ }
354
+ /**
355
+ * Upload the file in chunks with backpressure handling
356
+ *
357
+ * @param onProgress - Optional callback for progress updates (bytesUploaded, totalBytes)
358
+ */
359
+ async upload(e) {
360
+ const t = this.file.size;
361
+ for (let a = 0; a < this.totalChunks; a++) {
362
+ if (this.cancelled)
363
+ throw new Error("Upload cancelled");
364
+ if (this.channel.readyState !== "open")
365
+ throw new Error("Video upload interrupted");
366
+ const o = a * T, n = Math.min(o + T, t), i = this.file.slice(o, n), l = new Uint8Array(await i.arrayBuffer()), c = new ArrayBuffer(8 + l.length), s = new DataView(c);
367
+ for (s.setUint32(0, a, !0), s.setUint32(4, this.totalChunks, !0), new Uint8Array(c, 8).set(l); this.channel.bufferedAmount > j; ) {
368
+ if (this.channel.readyState !== "open")
369
+ throw new Error("Video upload interrupted");
370
+ await V(B);
371
+ }
372
+ this.channel.send(c), e && e(n, t);
373
+ }
374
+ }
375
+ }
376
+ const $ = 12;
377
+ class P {
247
378
  constructor() {
248
- u(this, "pendingFrames", /* @__PURE__ */ new Map());
379
+ d(this, "pendingFrames", /* @__PURE__ */ new Map());
249
380
  }
250
381
  /**
251
382
  * Process an incoming chunk and return the complete message if all chunks received
252
383
  */
253
- processChunk(e, t, n, r) {
254
- if (n === 1)
255
- return r;
384
+ processChunk(e, t, a, o) {
385
+ if (a === 1)
386
+ return o;
256
387
  this.pendingFrames.has(e) || this.pendingFrames.set(e, {
257
388
  chunks: /* @__PURE__ */ new Map(),
258
- totalChunks: n
389
+ totalChunks: a
259
390
  });
260
- const o = this.pendingFrames.get(e);
261
- if (o.chunks.set(t, r), o.chunks.size === n) {
262
- const i = Array.from(o.chunks.values()).reduce((c, l) => c + l.length, 0), s = new Uint8Array(i);
263
- let d = 0;
264
- for (let c = 0; c < n; c++) {
265
- const l = o.chunks.get(c);
266
- s.set(l, d), d += l.length;
391
+ const n = this.pendingFrames.get(e);
392
+ if (n.chunks.set(t, o), n.chunks.size === a) {
393
+ const i = Array.from(n.chunks.values()).reduce((s, u) => s + u.length, 0), l = new Uint8Array(i);
394
+ let c = 0;
395
+ for (let s = 0; s < a; s++) {
396
+ const u = n.chunks.get(s);
397
+ l.set(u, c), c += u.length;
267
398
  }
268
- return this.pendingFrames.delete(e), s;
399
+ return this.pendingFrames.delete(e), l;
269
400
  }
270
401
  return null;
271
402
  }
@@ -276,113 +407,139 @@ class b {
276
407
  this.pendingFrames.clear();
277
408
  }
278
409
  }
279
- function C(a) {
280
- const e = new DataView(a), t = e.getUint32(0, !0), n = e.getUint32(4, !0), r = e.getUint32(8, !0), o = new Uint8Array(a, F);
281
- return { frameId: t, chunkIndex: n, totalChunks: r, payload: o };
410
+ function L(r) {
411
+ const e = new DataView(r), t = e.getUint32(0, !0), a = e.getUint32(4, !0), o = e.getUint32(8, !0), n = new Uint8Array(r, $);
412
+ return { frameId: t, chunkIndex: a, totalChunks: o, payload: n };
282
413
  }
283
- async function P(a, e = 6e3) {
284
- if (a.iceGatheringState === "complete") return;
414
+ async function M(r, e = 6e3) {
415
+ if (r.iceGatheringState === "complete") return;
285
416
  let t = !1;
286
- const n = (r) => {
287
- r.candidate && r.candidate.type === "srflx" && (t = !0);
417
+ const a = (o) => {
418
+ o.candidate && o.candidate.type === "srflx" && (t = !0);
288
419
  };
289
- a.addEventListener("icecandidate", n);
420
+ r.addEventListener("icecandidate", a);
290
421
  try {
291
422
  await Promise.race([
292
- new Promise((r) => {
293
- const o = () => {
294
- a.iceGatheringState === "complete" && (a.removeEventListener("icegatheringstatechange", o), r());
423
+ new Promise((o) => {
424
+ const n = () => {
425
+ r.iceGatheringState === "complete" && (r.removeEventListener("icegatheringstatechange", n), o());
295
426
  };
296
- a.addEventListener("icegatheringstatechange", o);
427
+ r.addEventListener("icegatheringstatechange", n);
297
428
  }),
298
- new Promise((r, o) => {
429
+ new Promise((o, n) => {
299
430
  setTimeout(() => {
300
- t ? r() : (console.error("[ICE] timeout with NO srflx candidate! Connection may fail."), o(new Error("ICE gathering timeout without srflx candidate")));
431
+ t ? o() : (console.error("[ICE] timeout with NO srflx candidate! Connection may fail."), n(new Error("ICE gathering timeout without srflx candidate")));
301
432
  }, e);
302
433
  })
303
434
  ]);
304
435
  } finally {
305
- a.removeEventListener("icecandidate", n);
436
+ r.removeEventListener("icecandidate", a);
306
437
  }
307
438
  }
308
- function I(a) {
439
+ function z(r) {
309
440
  return new Promise((e) => {
310
- a.addEventListener("track", (t) => {
441
+ r.addEventListener("track", (t) => {
311
442
  t.streams && t.streams[0] && e(t.streams[0]);
312
443
  });
313
444
  });
314
445
  }
315
- const D = [
446
+ const H = [
316
447
  { urls: ["stun:stun.l.google.com:19302"] }
317
448
  ];
318
- async function x(a, e) {
319
- const t = e ?? D, n = new RTCPeerConnection({
320
- iceServers: t
449
+ async function G(r, e, t) {
450
+ if (!r && !e || r && e)
451
+ throw new Error("Either localStream or file must be provided, but not both");
452
+ const a = t ?? H, o = new RTCPeerConnection({
453
+ iceServers: a
321
454
  });
322
455
  try {
323
- n.addTransceiver("video", { direction: "recvonly" });
456
+ o.addTransceiver("video", { direction: "recvonly" });
324
457
  } catch (s) {
325
458
  console.warn("[RFWebRTC] Could not add transceiver:", s);
326
459
  }
327
- a.getVideoTracks().forEach((s) => {
460
+ r && r.getVideoTracks().forEach((s) => {
328
461
  try {
329
462
  s.contentHint = "detail";
330
463
  } catch {
331
464
  }
332
- n.addTrack(s, a);
465
+ o.addTrack(s, r);
333
466
  });
334
- const r = I(n), o = n.createDataChannel("roboflow-control", {
467
+ const n = z(o), i = o.createDataChannel("inference", {
335
468
  ordered: !0
336
- }), i = await n.createOffer();
337
- return await n.setLocalDescription(i), await P(n), {
338
- pc: n,
339
- offer: n.localDescription,
340
- remoteStreamPromise: r,
341
- dataChannel: o
469
+ });
470
+ let l;
471
+ e && (l = o.createDataChannel("video_upload"));
472
+ const c = await o.createOffer();
473
+ return await o.setLocalDescription(c), await M(o), {
474
+ pc: o,
475
+ offer: o.localDescription,
476
+ remoteStreamPromise: n,
477
+ dataChannel: i,
478
+ uploadChannel: l
342
479
  };
343
480
  }
344
- async function N(a) {
345
- const e = a.getSenders().find((n) => n.track && n.track.kind === "video");
481
+ async function J(r) {
482
+ const e = r.getSenders().find((a) => a.track && a.track.kind === "video");
346
483
  if (!e) return;
347
484
  const t = e.getParameters();
348
485
  t.encodings = t.encodings || [{}], t.encodings[0].scaleResolutionDownBy = 1;
349
486
  try {
350
487
  await e.setParameters(t);
351
- } catch (n) {
352
- console.warn("[RFWebRTC] Failed to set encoding parameters:", n);
488
+ } catch (a) {
489
+ console.warn("[RFWebRTC] Failed to set encoding parameters:", a);
353
490
  }
354
491
  }
355
- class R {
492
+ function Z(r, e = 3e4) {
493
+ return new Promise((t, a) => {
494
+ if (r.readyState === "open") {
495
+ t();
496
+ return;
497
+ }
498
+ const o = () => {
499
+ r.removeEventListener("open", o), r.removeEventListener("error", n), clearTimeout(i), t();
500
+ }, n = () => {
501
+ r.removeEventListener("open", o), r.removeEventListener("error", n), clearTimeout(i), a(new Error("Datachannel error"));
502
+ }, i = setTimeout(() => {
503
+ r.removeEventListener("open", o), r.removeEventListener("error", n), a(new Error("Datachannel open timeout"));
504
+ }, e);
505
+ r.addEventListener("open", o), r.addEventListener("error", n);
506
+ });
507
+ }
508
+ class O {
356
509
  /** @private */
357
- constructor(e, t, n, r, o, i, s) {
358
- u(this, "pc");
359
- u(this, "_localStream");
360
- u(this, "remoteStreamPromise");
361
- u(this, "pipelineId");
362
- u(this, "apiKey");
363
- u(this, "dataChannel");
364
- u(this, "reassembler");
365
- this.pc = e, this._localStream = t, this.remoteStreamPromise = n, this.pipelineId = r, this.apiKey = o, this.dataChannel = i, this.reassembler = new b(), this.dataChannel.binaryType = "arraybuffer", s && (this.dataChannel.addEventListener("open", () => {
366
- }), this.dataChannel.addEventListener("message", (d) => {
510
+ constructor(e, t, a, o, n, i) {
511
+ d(this, "pc");
512
+ d(this, "_localStream");
513
+ d(this, "remoteStreamPromise");
514
+ d(this, "pipelineId");
515
+ d(this, "apiKey");
516
+ d(this, "dataChannel");
517
+ d(this, "reassembler");
518
+ d(this, "uploadChannel");
519
+ d(this, "uploader");
520
+ d(this, "onComplete");
521
+ this.pc = e, this._localStream = i == null ? void 0 : i.localStream, this.remoteStreamPromise = t, this.pipelineId = a, this.apiKey = o, this.dataChannel = n, this.reassembler = new P(), this.uploadChannel = i == null ? void 0 : i.uploadChannel, this.onComplete = i == null ? void 0 : i.onComplete, this.dataChannel.binaryType = "arraybuffer";
522
+ const l = i == null ? void 0 : i.onData;
523
+ l && (this.dataChannel.addEventListener("message", (c) => {
367
524
  try {
368
- if (d.data instanceof ArrayBuffer) {
369
- const { frameId: c, chunkIndex: l, totalChunks: p, payload: y } = C(d.data), w = this.reassembler.processChunk(c, l, p, y);
525
+ if (c.data instanceof ArrayBuffer) {
526
+ const { frameId: s, chunkIndex: u, totalChunks: h, payload: y } = L(c.data), w = this.reassembler.processChunk(s, u, h, y);
370
527
  if (w) {
371
- const S = new TextDecoder("utf-8").decode(w), f = JSON.parse(S);
372
- s(f);
528
+ const g = new TextDecoder("utf-8").decode(w), v = JSON.parse(g);
529
+ l(v);
373
530
  }
374
531
  } else {
375
- const c = JSON.parse(d.data);
376
- s(c);
532
+ const s = JSON.parse(c.data);
533
+ l(s);
377
534
  }
378
- } catch (c) {
379
- console.error("[RFWebRTC] Failed to parse data channel message:", c);
535
+ } catch (s) {
536
+ console.error("[RFWebRTC] Failed to parse data channel message:", s);
380
537
  }
381
- }), this.dataChannel.addEventListener("error", (d) => {
382
- console.error("[RFWebRTC] Data channel error:", d);
383
- }), this.dataChannel.addEventListener("close", () => {
384
- this.reassembler.clear();
385
- }));
538
+ }), this.dataChannel.addEventListener("error", (c) => {
539
+ console.error("[RFWebRTC] Data channel error:", c);
540
+ })), this.dataChannel.addEventListener("close", () => {
541
+ this.reassembler.clear(), this.onComplete && this.onComplete();
542
+ });
386
543
  }
387
544
  /**
388
545
  * Get the remote stream (processed video from Roboflow)
@@ -402,13 +559,15 @@ class R {
402
559
  /**
403
560
  * Get the local stream (original camera)
404
561
  *
405
- * @returns The local MediaStream
562
+ * @returns The local MediaStream, or undefined if using file upload mode
406
563
  *
407
564
  * @example
408
565
  * ```typescript
409
566
  * const conn = await useStream({ ... });
410
567
  * const localStream = conn.localStream();
411
- * videoElement.srcObject = localStream;
568
+ * if (localStream) {
569
+ * videoElement.srcObject = localStream;
570
+ * }
412
571
  * ```
413
572
  */
414
573
  localStream() {
@@ -418,7 +577,7 @@ class R {
418
577
  * Cleanup and close connection
419
578
  *
420
579
  * Terminates the pipeline on Roboflow, closes the peer connection,
421
- * and stops the local media stream.
580
+ * and stops the local media stream (if applicable).
422
581
  *
423
582
  * @returns Promise that resolves when cleanup is complete
424
583
  *
@@ -430,7 +589,39 @@ class R {
430
589
  * ```
431
590
  */
432
591
  async cleanup() {
433
- this.reassembler.clear(), this.pipelineId && this.apiKey && await _.init({ apiKey: this.apiKey }).terminatePipeline({ pipelineId: this.pipelineId }), this.pc && this.pc.connectionState !== "closed" && this.pc.close(), v(this._localStream);
592
+ if (this.uploader && this.uploader.cancel(), this.reassembler.clear(), this.pipelineId && this.apiKey)
593
+ try {
594
+ await b.init({ apiKey: this.apiKey }).terminatePipeline({ pipelineId: this.pipelineId });
595
+ } catch (e) {
596
+ console.warn("[RFWebRTC] Failed to terminate pipeline:", e);
597
+ }
598
+ this.pc && this.pc.connectionState !== "closed" && this.pc.close(), this._localStream && W(this._localStream);
599
+ }
600
+ /**
601
+ * Start uploading a file through the connection
602
+ *
603
+ * @param file - The file to upload
604
+ * @param onProgress - Optional callback for progress updates (bytesUploaded, totalBytes)
605
+ * @returns Promise that resolves when upload is complete
606
+ * @throws Error if no upload channel is available
607
+ *
608
+ * @example
609
+ * ```typescript
610
+ * await connection.startUpload(videoFile, (uploaded, total) => {
611
+ * console.log(`Upload progress: ${(uploaded / total * 100).toFixed(1)}%`);
612
+ * });
613
+ * ```
614
+ */
615
+ async startUpload(e, t) {
616
+ if (!this.uploadChannel)
617
+ throw new Error("No upload channel available. This connection was not created for file uploads.");
618
+ await Z(this.uploadChannel), this.uploader = new I(e, this.uploadChannel), await this.uploader.upload(t);
619
+ }
620
+ /**
621
+ * Cancel any ongoing file upload
622
+ */
623
+ cancelUpload() {
624
+ this.uploader && this.uploader.cancel();
434
625
  }
435
626
  /**
436
627
  * Reconfigure pipeline outputs at runtime
@@ -485,47 +676,117 @@ class R {
485
676
  }
486
677
  }
487
678
  }
488
- async function U({
489
- source: a,
679
+ async function D({
680
+ source: r,
490
681
  connector: e,
491
682
  wrtcParams: t,
492
- onData: n,
493
- options: r = {}
683
+ onData: a,
684
+ onComplete: o,
685
+ onFileUploadProgress: n,
686
+ options: i = {}
494
687
  }) {
495
- var f;
688
+ var k;
496
689
  if (!e || typeof e.connectWrtc != "function")
497
690
  throw new Error("connector must have a connectWrtc method");
498
- const o = a, { pc: i, offer: s, remoteStreamPromise: d, dataChannel: c } = await x(
499
- o,
500
- t.iceServers
501
- ), l = await e.connectWrtc(
502
- { sdp: s.sdp, type: s.type },
503
- t
504
- ), p = { sdp: l.sdp, type: l.type };
691
+ const l = r instanceof File, c = l ? void 0 : r, s = l ? r : void 0;
692
+ let u = t.iceServers;
693
+ if ((!u || u.length === 0) && e.getIceServers)
694
+ try {
695
+ const f = await e.getIceServers();
696
+ f && f.length > 0 && (u = f, console.log("[RFWebRTC] Using TURN servers from connector"));
697
+ } catch (f) {
698
+ console.warn("[RFWebRTC] Failed to fetch TURN config, using defaults:", f);
699
+ }
700
+ const { pc: h, offer: y, remoteStreamPromise: w, dataChannel: R, uploadChannel: g } = await G(
701
+ c,
702
+ s,
703
+ u
704
+ ), v = {
705
+ ...t,
706
+ iceServers: u,
707
+ realtimeProcessing: t.realtimeProcessing ?? !l
708
+ }, m = await e.connectWrtc(
709
+ { sdp: y.sdp, type: y.type },
710
+ v
711
+ ), p = { sdp: m.sdp, type: m.type };
505
712
  if (!(p != null && p.sdp) || !(p != null && p.type))
506
- throw console.error("[RFWebRTC] Invalid answer from server:", l), new Error("connector.connectWrtc must return answer with sdp and type");
507
- const y = ((f = l == null ? void 0 : l.context) == null ? void 0 : f.pipeline_id) || null;
508
- await i.setRemoteDescription(p), await new Promise((g, m) => {
509
- const h = () => {
510
- i.connectionState === "connected" ? (i.removeEventListener("connectionstatechange", h), g()) : i.connectionState === "failed" && (i.removeEventListener("connectionstatechange", h), m(new Error("WebRTC connection failed")));
713
+ throw console.error("[RFWebRTC] Invalid answer from server:", m), new Error("connector.connectWrtc must return answer with sdp and type");
714
+ const S = ((k = m == null ? void 0 : m.context) == null ? void 0 : k.pipeline_id) || null;
715
+ await h.setRemoteDescription(p), await new Promise((f, F) => {
716
+ const _ = () => {
717
+ h.connectionState === "connected" ? (h.removeEventListener("connectionstatechange", _), f()) : h.connectionState === "failed" && (h.removeEventListener("connectionstatechange", _), F(new Error("WebRTC connection failed")));
511
718
  };
512
- i.addEventListener("connectionstatechange", h), h(), setTimeout(() => {
513
- i.removeEventListener("connectionstatechange", h), m(new Error("WebRTC connection timeout after 30s"));
719
+ h.addEventListener("connectionstatechange", _), _(), setTimeout(() => {
720
+ h.removeEventListener("connectionstatechange", _), F(new Error("WebRTC connection timeout after 30s"));
514
721
  }, 3e4);
515
- }), r.disableInputStreamDownscaling !== !1 && await N(i);
516
- const k = e._apiKey || null;
517
- return new R(i, o, d, y, k, c, n);
722
+ }), c && i.disableInputStreamDownscaling !== !1 && await J(h);
723
+ const C = e._apiKey || null, E = new O(
724
+ h,
725
+ w,
726
+ S,
727
+ C,
728
+ R,
729
+ {
730
+ localStream: c,
731
+ uploadChannel: g,
732
+ onData: a,
733
+ onComplete: o
734
+ }
735
+ );
736
+ return s && g && E.startUpload(s, n).catch((f) => {
737
+ console.error("[RFWebRTC] Upload error:", f);
738
+ }), E;
739
+ }
740
+ async function Q({
741
+ source: r,
742
+ connector: e,
743
+ wrtcParams: t,
744
+ onData: a,
745
+ options: o = {}
746
+ }) {
747
+ if (r instanceof File)
748
+ throw new Error("useStream requires a MediaStream. Use useVideoFile for File uploads.");
749
+ return D({
750
+ source: r,
751
+ connector: e,
752
+ wrtcParams: t,
753
+ onData: a,
754
+ options: o
755
+ });
756
+ }
757
+ async function X({
758
+ file: r,
759
+ connector: e,
760
+ wrtcParams: t,
761
+ onData: a,
762
+ onUploadProgress: o,
763
+ onComplete: n
764
+ }) {
765
+ return D({
766
+ source: r,
767
+ connector: e,
768
+ wrtcParams: {
769
+ ...t,
770
+ realtimeProcessing: t.realtimeProcessing ?? !0
771
+ },
772
+ onData: a,
773
+ onComplete: n,
774
+ onFileUploadProgress: o
775
+ });
518
776
  }
519
- const $ = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
777
+ const re = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
520
778
  __proto__: null,
521
- ChunkReassembler: b,
522
- RFWebRTCConnection: R,
523
- parseBinaryHeader: C,
524
- useStream: U
779
+ ChunkReassembler: P,
780
+ FileUploader: I,
781
+ RFWebRTCConnection: O,
782
+ parseBinaryHeader: L,
783
+ useStream: Q,
784
+ useVideoFile: X
525
785
  }, Symbol.toStringTag, { value: "Module" }));
526
786
  export {
527
- _ as InferenceHTTPClient,
528
- K as connectors,
529
- j as streams,
530
- $ as webrtc
787
+ b as InferenceHTTPClient,
788
+ ee as connectors,
789
+ te as streams,
790
+ re as webrtc
531
791
  };
792
+ //# sourceMappingURL=index.es.js.map