@roboflow/inference-sdk 0.1.7 → 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,24 +1,24 @@
1
- var W = Object.defineProperty;
2
- var U = (o, e, t) => e in o ? W(o, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[e] = t;
3
- var p = (o, e, t) => U(o, typeof e != "symbol" ? e + "" : e, t);
4
- var b;
5
- const I = typeof process < "u" && ((b = process.env) != null && b.RF_API_BASE_URL) ? process.env.RF_API_BASE_URL : "https://api.roboflow.com", O = [
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
6
  "https://serverless.roboflow.com"
7
7
  ];
8
- class v {
8
+ class b {
9
9
  /**
10
10
  * @private
11
11
  * Use InferenceHTTPClient.init() instead
12
12
  */
13
13
  constructor(e, t = "https://serverless.roboflow.com") {
14
- p(this, "apiKey");
15
- p(this, "serverUrl");
14
+ d(this, "apiKey");
15
+ d(this, "serverUrl");
16
16
  this.apiKey = e, this.serverUrl = t;
17
17
  }
18
18
  static init({ apiKey: e, serverUrl: t }) {
19
19
  if (!e)
20
20
  throw new Error("apiKey is required");
21
- return new v(e, t);
21
+ return new b(e, t);
22
22
  }
23
23
  /**
24
24
  * Initialize a WebRTC worker pipeline
@@ -48,59 +48,60 @@ class v {
48
48
  async initializeWebrtcWorker({
49
49
  offer: e,
50
50
  workflowSpec: t,
51
- workspaceName: r,
52
- workflowId: a,
51
+ workspaceName: a,
52
+ workflowId: o,
53
53
  config: n = {}
54
54
  }) {
55
55
  if (!e || !e.sdp || !e.type)
56
56
  throw new Error("offer with sdp and type is required");
57
- const i = !!t, s = !!(r && a);
58
- if (!i && !s)
57
+ const i = !!t, l = !!(a && o);
58
+ if (!i && !l)
59
59
  throw new Error("Either workflowSpec OR (workspaceName + workflowId) is required");
60
- if (i && s)
60
+ if (i && l)
61
61
  throw new Error("Provide either workflowSpec OR (workspaceName + workflowId), not both");
62
62
  const {
63
- imageInputName: l = "image",
64
- streamOutputNames: c = [],
65
- dataOutputNames: h = ["string"],
66
- threadPoolWorkers: y = 4,
67
- workflowsParameters: f = {},
68
- iceServers: d,
69
- processingTimeout: S,
70
- requestedPlan: _,
71
- requestedRegion: g
72
- } = n, R = {
63
+ imageInputName: c = "image",
64
+ streamOutputNames: s = [],
65
+ dataOutputNames: u = ["string"],
66
+ threadPoolWorkers: h = 4,
67
+ workflowsParameters: y = {},
68
+ iceServers: w,
69
+ processingTimeout: R,
70
+ requestedPlan: g,
71
+ requestedRegion: v,
72
+ realtimeProcessing: m = !0
73
+ } = n, p = {
73
74
  type: "WorkflowConfiguration",
74
- image_input_name: l,
75
- workflows_parameters: f,
76
- workflows_thread_pool_workers: y,
75
+ image_input_name: c,
76
+ workflows_parameters: y,
77
+ workflows_thread_pool_workers: h,
77
78
  cancel_thread_pool_tasks_on_exit: !0,
78
79
  video_metadata_input_name: "video_metadata"
79
80
  };
80
- i ? R.workflow_specification = t : (R.workspace_name = r, R.workflow_id = a);
81
- const m = {
82
- workflow_configuration: R,
81
+ i ? p.workflow_specification = t : (p.workspace_name = a, p.workflow_id = o);
82
+ const S = {
83
+ workflow_configuration: p,
83
84
  api_key: this.apiKey,
84
- webrtc_realtime_processing: !0,
85
+ webrtc_realtime_processing: m,
85
86
  webrtc_offer: {
86
87
  sdp: e.sdp,
87
88
  type: e.type
88
89
  },
89
- webrtc_config: d ? { iceServers: d } : null,
90
- stream_output: c,
91
- data_output: h
90
+ webrtc_config: w ? { iceServers: w } : null,
91
+ stream_output: s,
92
+ data_output: u
92
93
  };
93
- S !== void 0 && (m.processing_timeout = S), _ !== void 0 && (m.requested_plan = _), g !== void 0 && (m.requested_region = g), console.trace("payload", m);
94
- const u = 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`, {
95
96
  method: "POST",
96
97
  headers: { "Content-Type": "application/json" },
97
- body: JSON.stringify(m)
98
+ body: JSON.stringify(S)
98
99
  });
99
- if (!u.ok) {
100
- const w = await u.text().catch(() => "");
101
- throw new Error(`initialise_webrtc_worker failed (${u.status}): ${w}`);
100
+ if (!C.ok) {
101
+ const k = await C.text().catch(() => "");
102
+ throw new Error(`initialise_webrtc_worker failed (${C.status}): ${k}`);
102
103
  }
103
- return await u.json();
104
+ return await C.json();
104
105
  }
105
106
  async terminatePipeline({ pipelineId: e }) {
106
107
  if (!e)
@@ -130,11 +131,11 @@ class v {
130
131
  * ```
131
132
  */
132
133
  async fetchTurnConfig() {
133
- if (!O.includes(this.serverUrl))
134
+ if (!q.includes(this.serverUrl))
134
135
  return null;
135
136
  try {
136
137
  const e = await fetch(
137
- `${I}/webrtc_turn_config?api_key=${this.apiKey}`,
138
+ `${x}/webrtc_turn_config?api_key=${this.apiKey}`,
138
139
  {
139
140
  method: "GET",
140
141
  headers: { "Content-Type": "application/json" }
@@ -143,16 +144,16 @@ class v {
143
144
  if (!e.ok)
144
145
  return console.warn(`[RFWebRTC] Failed to fetch TURN config (${e.status}), using defaults`), null;
145
146
  const t = await e.json();
146
- let r;
147
+ let a;
147
148
  if (Array.isArray(t))
148
- r = t;
149
+ a = t;
149
150
  else if (t.iceServers && Array.isArray(t.iceServers))
150
- r = t.iceServers;
151
+ a = t.iceServers;
151
152
  else if (t.urls)
152
- r = [t];
153
+ a = [t];
153
154
  else
154
155
  return console.warn("[RFWebRTC] Invalid TURN config format, using defaults"), null;
155
- return r.map((n) => ({
156
+ return a.map((n) => ({
156
157
  urls: Array.isArray(n.urls) ? n.urls : [n.urls],
157
158
  username: n.username,
158
159
  credential: n.credential
@@ -162,7 +163,7 @@ class v {
162
163
  }
163
164
  }
164
165
  }
165
- const $ = {
166
+ const ee = {
166
167
  /**
167
168
  * Create a connector that uses API key directly
168
169
  *
@@ -181,15 +182,15 @@ const $ = {
181
182
  * const answer = await connector.connectWrtc(offer, wrtcParams);
182
183
  * ```
183
184
  */
184
- withApiKey(o, e = {}) {
185
+ withApiKey(r, e = {}) {
185
186
  const { serverUrl: t } = e;
186
187
  typeof window < "u" && console.warn(
187
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"
188
189
  );
189
- const r = v.init({ apiKey: o, serverUrl: t });
190
+ const a = b.init({ apiKey: r, serverUrl: t });
190
191
  return {
191
- connectWrtc: async (a, n) => (console.log("wrtcParams", n), await r.initializeWebrtcWorker({
192
- offer: a,
192
+ connectWrtc: async (o, n) => (console.debug("wrtcParams", n), await a.initializeWebrtcWorker({
193
+ offer: o,
193
194
  workflowSpec: n.workflowSpec,
194
195
  workspaceName: n.workspaceName,
195
196
  workflowId: n.workflowId,
@@ -202,15 +203,16 @@ const $ = {
202
203
  iceServers: n.iceServers,
203
204
  processingTimeout: n.processingTimeout,
204
205
  requestedPlan: n.requestedPlan,
205
- requestedRegion: n.requestedRegion
206
+ requestedRegion: n.requestedRegion,
207
+ realtimeProcessing: n.realtimeProcessing
206
208
  }
207
209
  })),
208
210
  /**
209
211
  * Fetch TURN server configuration for improved WebRTC connectivity
210
212
  */
211
- getIceServers: async () => await r.fetchTurnConfig(),
213
+ getIceServers: async () => await a.fetchTurnConfig(),
212
214
  // Store apiKey for cleanup
213
- _apiKey: o,
215
+ _apiKey: r,
214
216
  _serverUrl: t
215
217
  };
216
218
  },
@@ -277,16 +279,16 @@ const $ = {
277
279
  * });
278
280
  * ```
279
281
  */
280
- withProxyUrl(o, e = {}) {
282
+ withProxyUrl(r, e = {}) {
281
283
  const { turnConfigUrl: t } = e;
282
284
  return {
283
- connectWrtc: async (r, a) => {
284
- const n = await fetch(o, {
285
+ connectWrtc: async (a, o) => {
286
+ const n = await fetch(r, {
285
287
  method: "POST",
286
288
  headers: { "Content-Type": "application/json" },
287
289
  body: JSON.stringify({
288
- offer: r,
289
- wrtcParams: a
290
+ offer: a,
291
+ wrtcParams: o
290
292
  })
291
293
  });
292
294
  if (!n.ok) {
@@ -301,60 +303,100 @@ const $ = {
301
303
  */
302
304
  getIceServers: t ? async () => {
303
305
  try {
304
- const r = await fetch(t, {
306
+ const a = await fetch(t, {
305
307
  method: "GET",
306
308
  headers: { "Content-Type": "application/json" }
307
309
  });
308
- return r.ok ? (await r.json()).iceServers || null : (console.warn(`[RFWebRTC] Failed to fetch TURN config from proxy (${r.status})`), null);
309
- } catch (r) {
310
- return console.warn("[RFWebRTC] Error fetching TURN config from proxy:", r), null;
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;
311
313
  }
312
314
  } : void 0
313
315
  };
314
316
  }
315
317
  };
316
- async function P(o = { video: !0 }) {
318
+ async function K(r = { video: !0 }) {
317
319
  try {
318
- console.log("[RFStreams] requesting with", o);
319
- const e = await navigator.mediaDevices.getUserMedia(o);
320
+ console.log("[RFStreams] requesting with", r);
321
+ const e = await navigator.mediaDevices.getUserMedia(r);
320
322
  return console.log("[RFStreams] got stream", e.getVideoTracks().map((t) => ({ id: t.id, label: t.label }))), e;
321
323
  } catch (e) {
322
324
  console.warn("[RFStreams] failed, falling back", e);
323
325
  const t = await navigator.mediaDevices.getUserMedia({ video: !0, audio: !1 });
324
- return console.log("[RFStreams] fallback stream", t.getVideoTracks().map((r) => ({ id: r.id, label: r.label }))), t;
326
+ return console.log("[RFStreams] fallback stream", t.getVideoTracks().map((a) => ({ id: a.id, label: a.label }))), t;
325
327
  }
326
328
  }
327
- function C(o) {
328
- o && (o.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"));
329
331
  }
330
- const B = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
332
+ const te = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
331
333
  __proto__: null,
332
- stopStream: C,
333
- useCamera: P
334
- }, Symbol.toStringTag, { value: "Module" })), N = 12;
335
- class T {
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 {
336
378
  constructor() {
337
- p(this, "pendingFrames", /* @__PURE__ */ new Map());
379
+ d(this, "pendingFrames", /* @__PURE__ */ new Map());
338
380
  }
339
381
  /**
340
382
  * Process an incoming chunk and return the complete message if all chunks received
341
383
  */
342
- processChunk(e, t, r, a) {
343
- if (r === 1)
344
- return a;
384
+ processChunk(e, t, a, o) {
385
+ if (a === 1)
386
+ return o;
345
387
  this.pendingFrames.has(e) || this.pendingFrames.set(e, {
346
388
  chunks: /* @__PURE__ */ new Map(),
347
- totalChunks: r
389
+ totalChunks: a
348
390
  });
349
391
  const n = this.pendingFrames.get(e);
350
- if (n.chunks.set(t, a), n.chunks.size === r) {
351
- const i = Array.from(n.chunks.values()).reduce((c, h) => c + h.length, 0), s = new Uint8Array(i);
352
- let l = 0;
353
- for (let c = 0; c < r; c++) {
354
- const h = n.chunks.get(c);
355
- s.set(h, l), l += h.length;
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;
356
398
  }
357
- return this.pendingFrames.delete(e), s;
399
+ return this.pendingFrames.delete(e), l;
358
400
  }
359
401
  return null;
360
402
  }
@@ -365,113 +407,139 @@ class T {
365
407
  this.pendingFrames.clear();
366
408
  }
367
409
  }
368
- function E(o) {
369
- const e = new DataView(o), t = e.getUint32(0, !0), r = e.getUint32(4, !0), a = e.getUint32(8, !0), n = new Uint8Array(o, N);
370
- return { frameId: t, chunkIndex: r, totalChunks: a, payload: n };
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 };
371
413
  }
372
- async function L(o, e = 6e3) {
373
- if (o.iceGatheringState === "complete") return;
414
+ async function M(r, e = 6e3) {
415
+ if (r.iceGatheringState === "complete") return;
374
416
  let t = !1;
375
- const r = (a) => {
376
- a.candidate && a.candidate.type === "srflx" && (t = !0);
417
+ const a = (o) => {
418
+ o.candidate && o.candidate.type === "srflx" && (t = !0);
377
419
  };
378
- o.addEventListener("icecandidate", r);
420
+ r.addEventListener("icecandidate", a);
379
421
  try {
380
422
  await Promise.race([
381
- new Promise((a) => {
423
+ new Promise((o) => {
382
424
  const n = () => {
383
- o.iceGatheringState === "complete" && (o.removeEventListener("icegatheringstatechange", n), a());
425
+ r.iceGatheringState === "complete" && (r.removeEventListener("icegatheringstatechange", n), o());
384
426
  };
385
- o.addEventListener("icegatheringstatechange", n);
427
+ r.addEventListener("icegatheringstatechange", n);
386
428
  }),
387
- new Promise((a, n) => {
429
+ new Promise((o, n) => {
388
430
  setTimeout(() => {
389
- t ? a() : (console.error("[ICE] timeout with NO srflx candidate! Connection may fail."), n(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")));
390
432
  }, e);
391
433
  })
392
434
  ]);
393
435
  } finally {
394
- o.removeEventListener("icecandidate", r);
436
+ r.removeEventListener("icecandidate", a);
395
437
  }
396
438
  }
397
- function x(o) {
439
+ function z(r) {
398
440
  return new Promise((e) => {
399
- o.addEventListener("track", (t) => {
441
+ r.addEventListener("track", (t) => {
400
442
  t.streams && t.streams[0] && e(t.streams[0]);
401
443
  });
402
444
  });
403
445
  }
404
- const D = [
446
+ const H = [
405
447
  { urls: ["stun:stun.l.google.com:19302"] }
406
448
  ];
407
- async function A(o, e) {
408
- const t = e ?? D, r = new RTCPeerConnection({
409
- 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
410
454
  });
411
455
  try {
412
- r.addTransceiver("video", { direction: "recvonly" });
456
+ o.addTransceiver("video", { direction: "recvonly" });
413
457
  } catch (s) {
414
458
  console.warn("[RFWebRTC] Could not add transceiver:", s);
415
459
  }
416
- o.getVideoTracks().forEach((s) => {
460
+ r && r.getVideoTracks().forEach((s) => {
417
461
  try {
418
462
  s.contentHint = "detail";
419
463
  } catch {
420
464
  }
421
- r.addTrack(s, o);
465
+ o.addTrack(s, r);
422
466
  });
423
- const a = x(r), n = r.createDataChannel("roboflow-control", {
467
+ const n = z(o), i = o.createDataChannel("inference", {
424
468
  ordered: !0
425
- }), i = await r.createOffer();
426
- return await r.setLocalDescription(i), await L(r), {
427
- pc: r,
428
- offer: r.localDescription,
429
- remoteStreamPromise: a,
430
- dataChannel: n
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
431
479
  };
432
480
  }
433
- async function q(o) {
434
- const e = o.getSenders().find((r) => r.track && r.track.kind === "video");
481
+ async function J(r) {
482
+ const e = r.getSenders().find((a) => a.track && a.track.kind === "video");
435
483
  if (!e) return;
436
484
  const t = e.getParameters();
437
485
  t.encodings = t.encodings || [{}], t.encodings[0].scaleResolutionDownBy = 1;
438
486
  try {
439
487
  await e.setParameters(t);
440
- } catch (r) {
441
- console.warn("[RFWebRTC] Failed to set encoding parameters:", r);
488
+ } catch (a) {
489
+ console.warn("[RFWebRTC] Failed to set encoding parameters:", a);
442
490
  }
443
491
  }
444
- class F {
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 {
445
509
  /** @private */
446
- constructor(e, t, r, a, n, i, s) {
447
- p(this, "pc");
448
- p(this, "_localStream");
449
- p(this, "remoteStreamPromise");
450
- p(this, "pipelineId");
451
- p(this, "apiKey");
452
- p(this, "dataChannel");
453
- p(this, "reassembler");
454
- this.pc = e, this._localStream = t, this.remoteStreamPromise = r, this.pipelineId = a, this.apiKey = n, this.dataChannel = i, this.reassembler = new T(), this.dataChannel.binaryType = "arraybuffer", s && (this.dataChannel.addEventListener("open", () => {
455
- }), this.dataChannel.addEventListener("message", (l) => {
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) => {
456
524
  try {
457
- if (l.data instanceof ArrayBuffer) {
458
- const { frameId: c, chunkIndex: h, totalChunks: y, payload: f } = E(l.data), d = this.reassembler.processChunk(c, h, y, f);
459
- if (d) {
460
- const _ = new TextDecoder("utf-8").decode(d), g = JSON.parse(_);
461
- s(g);
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);
527
+ if (w) {
528
+ const g = new TextDecoder("utf-8").decode(w), v = JSON.parse(g);
529
+ l(v);
462
530
  }
463
531
  } else {
464
- const c = JSON.parse(l.data);
465
- s(c);
532
+ const s = JSON.parse(c.data);
533
+ l(s);
466
534
  }
467
- } catch (c) {
468
- console.error("[RFWebRTC] Failed to parse data channel message:", c);
535
+ } catch (s) {
536
+ console.error("[RFWebRTC] Failed to parse data channel message:", s);
469
537
  }
470
- }), this.dataChannel.addEventListener("error", (l) => {
471
- console.error("[RFWebRTC] Data channel error:", l);
472
- }), this.dataChannel.addEventListener("close", () => {
473
- this.reassembler.clear();
474
- }));
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
+ });
475
543
  }
476
544
  /**
477
545
  * Get the remote stream (processed video from Roboflow)
@@ -491,13 +559,15 @@ class F {
491
559
  /**
492
560
  * Get the local stream (original camera)
493
561
  *
494
- * @returns The local MediaStream
562
+ * @returns The local MediaStream, or undefined if using file upload mode
495
563
  *
496
564
  * @example
497
565
  * ```typescript
498
566
  * const conn = await useStream({ ... });
499
567
  * const localStream = conn.localStream();
500
- * videoElement.srcObject = localStream;
568
+ * if (localStream) {
569
+ * videoElement.srcObject = localStream;
570
+ * }
501
571
  * ```
502
572
  */
503
573
  localStream() {
@@ -507,7 +577,7 @@ class F {
507
577
  * Cleanup and close connection
508
578
  *
509
579
  * Terminates the pipeline on Roboflow, closes the peer connection,
510
- * and stops the local media stream.
580
+ * and stops the local media stream (if applicable).
511
581
  *
512
582
  * @returns Promise that resolves when cleanup is complete
513
583
  *
@@ -519,7 +589,39 @@ class F {
519
589
  * ```
520
590
  */
521
591
  async cleanup() {
522
- this.reassembler.clear(), this.pipelineId && this.apiKey && await v.init({ apiKey: this.apiKey }).terminatePipeline({ pipelineId: this.pipelineId }), this.pc && this.pc.connectionState !== "closed" && this.pc.close(), C(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();
523
625
  }
524
626
  /**
525
627
  * Reconfigure pipeline outputs at runtime
@@ -574,59 +676,117 @@ class F {
574
676
  }
575
677
  }
576
678
  }
577
- async function j({
578
- source: o,
679
+ async function D({
680
+ source: r,
579
681
  connector: e,
580
682
  wrtcParams: t,
581
- onData: r,
582
- options: a = {}
683
+ onData: a,
684
+ onComplete: o,
685
+ onFileUploadProgress: n,
686
+ options: i = {}
583
687
  }) {
584
- var m;
688
+ var k;
585
689
  if (!e || typeof e.connectWrtc != "function")
586
690
  throw new Error("connector must have a connectWrtc method");
587
- const n = o;
588
- let i = t.iceServers;
589
- if ((!i || i.length === 0) && e.getIceServers)
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)
590
694
  try {
591
- const u = await e.getIceServers();
592
- u && u.length > 0 && (i = u, console.log("[RFWebRTC] Using TURN servers from connector"));
593
- } catch (u) {
594
- console.warn("[RFWebRTC] Failed to fetch TURN config, using defaults:", u);
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);
595
699
  }
596
- const { pc: s, offer: l, remoteStreamPromise: c, dataChannel: h } = await A(
597
- n,
598
- i
599
- ), y = {
700
+ const { pc: h, offer: y, remoteStreamPromise: w, dataChannel: R, uploadChannel: g } = await G(
701
+ c,
702
+ s,
703
+ u
704
+ ), v = {
600
705
  ...t,
601
- iceServers: i
602
- }, f = await e.connectWrtc(
603
- { sdp: l.sdp, type: l.type },
604
- y
605
- ), d = { sdp: f.sdp, type: f.type };
606
- if (!(d != null && d.sdp) || !(d != null && d.type))
607
- throw console.error("[RFWebRTC] Invalid answer from server:", f), new Error("connector.connectWrtc must return answer with sdp and type");
608
- const S = ((m = f == null ? void 0 : f.context) == null ? void 0 : m.pipeline_id) || null;
609
- await s.setRemoteDescription(d), await new Promise((u, k) => {
610
- const w = () => {
611
- s.connectionState === "connected" ? (s.removeEventListener("connectionstatechange", w), u()) : s.connectionState === "failed" && (s.removeEventListener("connectionstatechange", w), k(new Error("WebRTC connection failed")));
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 };
712
+ if (!(p != null && p.sdp) || !(p != null && p.type))
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")));
612
718
  };
613
- s.addEventListener("connectionstatechange", w), w(), setTimeout(() => {
614
- s.removeEventListener("connectionstatechange", w), k(new Error("WebRTC connection timeout after 30s"));
719
+ h.addEventListener("connectionstatechange", _), _(), setTimeout(() => {
720
+ h.removeEventListener("connectionstatechange", _), F(new Error("WebRTC connection timeout after 30s"));
615
721
  }, 3e4);
616
- }), a.disableInputStreamDownscaling !== !1 && await q(s);
617
- const g = e._apiKey || null;
618
- return new F(s, n, c, S, g, h, r);
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
+ });
619
776
  }
620
- const M = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
777
+ const re = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
621
778
  __proto__: null,
622
- ChunkReassembler: T,
623
- RFWebRTCConnection: F,
624
- parseBinaryHeader: E,
625
- useStream: j
779
+ ChunkReassembler: P,
780
+ FileUploader: I,
781
+ RFWebRTCConnection: O,
782
+ parseBinaryHeader: L,
783
+ useStream: Q,
784
+ useVideoFile: X
626
785
  }, Symbol.toStringTag, { value: "Module" }));
627
786
  export {
628
- v as InferenceHTTPClient,
629
- $ as connectors,
630
- B as streams,
631
- M as webrtc
787
+ b as InferenceHTTPClient,
788
+ ee as connectors,
789
+ te as streams,
790
+ re as webrtc
632
791
  };
792
+ //# sourceMappingURL=index.es.js.map