@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.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +456 -195
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -0
- package/dist/inference-api.d.ts +64 -7
- package/dist/inference-api.d.ts.map +1 -1
- package/dist/video-upload.d.ts +30 -0
- package/dist/video-upload.d.ts.map +1 -0
- package/dist/webrtc-types.d.ts +44 -0
- package/dist/webrtc-types.d.ts.map +1 -0
- package/dist/webrtc.d.ts +98 -8
- package/dist/webrtc.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/index.es.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
4
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
|
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:
|
|
48
|
-
workflowId:
|
|
49
|
-
config:
|
|
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,
|
|
54
|
-
if (!i && !
|
|
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 &&
|
|
60
|
+
if (i && l)
|
|
57
61
|
throw new Error("Provide either workflowSpec OR (workspaceName + workflowId), not both");
|
|
58
62
|
const {
|
|
59
|
-
imageInputName:
|
|
60
|
-
streamOutputNames:
|
|
61
|
-
dataOutputNames:
|
|
62
|
-
threadPoolWorkers:
|
|
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:
|
|
66
|
-
requestedPlan:
|
|
67
|
-
requestedRegion:
|
|
68
|
-
|
|
69
|
+
processingTimeout: R,
|
|
70
|
+
requestedPlan: g,
|
|
71
|
+
requestedRegion: v,
|
|
72
|
+
realtimeProcessing: m = !0
|
|
73
|
+
} = n, p = {
|
|
69
74
|
type: "WorkflowConfiguration",
|
|
70
|
-
image_input_name:
|
|
75
|
+
image_input_name: c,
|
|
71
76
|
workflows_parameters: y,
|
|
72
|
-
workflows_thread_pool_workers:
|
|
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 ?
|
|
77
|
-
const
|
|
78
|
-
workflow_configuration:
|
|
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:
|
|
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:
|
|
87
|
-
data_output:
|
|
91
|
+
stream_output: s,
|
|
92
|
+
data_output: u
|
|
88
93
|
};
|
|
89
|
-
|
|
90
|
-
const
|
|
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(
|
|
98
|
+
body: JSON.stringify(S)
|
|
94
99
|
});
|
|
95
|
-
if (!
|
|
96
|
-
const
|
|
97
|
-
throw new Error(`initialise_webrtc_worker failed (${
|
|
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
|
|
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
|
|
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(
|
|
185
|
+
withApiKey(r, e = {}) {
|
|
133
186
|
const { serverUrl: t } = e;
|
|
134
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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:
|
|
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
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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
|
-
*
|
|
175
|
-
* const
|
|
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(
|
|
282
|
+
withProxyUrl(r, e = {}) {
|
|
283
|
+
const { turnConfigUrl: t } = e;
|
|
208
284
|
return {
|
|
209
|
-
connectWrtc: async (
|
|
210
|
-
const
|
|
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:
|
|
215
|
-
wrtcParams:
|
|
290
|
+
offer: a,
|
|
291
|
+
wrtcParams: o
|
|
216
292
|
})
|
|
217
293
|
});
|
|
218
|
-
if (!
|
|
219
|
-
const
|
|
220
|
-
throw new Error(`Proxy request failed (${
|
|
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
|
|
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
|
|
318
|
+
async function K(r = { video: !0 }) {
|
|
228
319
|
try {
|
|
229
|
-
console.log("[RFStreams] requesting with",
|
|
230
|
-
const e = await navigator.mediaDevices.getUserMedia(
|
|
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((
|
|
326
|
+
return console.log("[RFStreams] fallback stream", t.getVideoTracks().map((a) => ({ id: a.id, label: a.label }))), t;
|
|
236
327
|
}
|
|
237
328
|
}
|
|
238
|
-
function
|
|
239
|
-
|
|
329
|
+
function W(r) {
|
|
330
|
+
r && (r.getTracks().forEach((e) => e.stop()), console.log("[RFStreams] Stream stopped"));
|
|
240
331
|
}
|
|
241
|
-
const
|
|
332
|
+
const te = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
242
333
|
__proto__: null,
|
|
243
|
-
stopStream:
|
|
244
|
-
useCamera:
|
|
245
|
-
}, Symbol.toStringTag, { value: "Module" })),
|
|
246
|
-
|
|
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
|
-
|
|
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,
|
|
254
|
-
if (
|
|
255
|
-
return
|
|
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:
|
|
389
|
+
totalChunks: a
|
|
259
390
|
});
|
|
260
|
-
const
|
|
261
|
-
if (
|
|
262
|
-
const i = Array.from(
|
|
263
|
-
let
|
|
264
|
-
for (let
|
|
265
|
-
const
|
|
266
|
-
|
|
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),
|
|
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
|
|
280
|
-
const e = new DataView(
|
|
281
|
-
return { frameId: t, chunkIndex:
|
|
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
|
|
284
|
-
if (
|
|
414
|
+
async function M(r, e = 6e3) {
|
|
415
|
+
if (r.iceGatheringState === "complete") return;
|
|
285
416
|
let t = !1;
|
|
286
|
-
const
|
|
287
|
-
|
|
417
|
+
const a = (o) => {
|
|
418
|
+
o.candidate && o.candidate.type === "srflx" && (t = !0);
|
|
288
419
|
};
|
|
289
|
-
|
|
420
|
+
r.addEventListener("icecandidate", a);
|
|
290
421
|
try {
|
|
291
422
|
await Promise.race([
|
|
292
|
-
new Promise((
|
|
293
|
-
const
|
|
294
|
-
|
|
423
|
+
new Promise((o) => {
|
|
424
|
+
const n = () => {
|
|
425
|
+
r.iceGatheringState === "complete" && (r.removeEventListener("icegatheringstatechange", n), o());
|
|
295
426
|
};
|
|
296
|
-
|
|
427
|
+
r.addEventListener("icegatheringstatechange", n);
|
|
297
428
|
}),
|
|
298
|
-
new Promise((
|
|
429
|
+
new Promise((o, n) => {
|
|
299
430
|
setTimeout(() => {
|
|
300
|
-
t ?
|
|
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
|
-
|
|
436
|
+
r.removeEventListener("icecandidate", a);
|
|
306
437
|
}
|
|
307
438
|
}
|
|
308
|
-
function
|
|
439
|
+
function z(r) {
|
|
309
440
|
return new Promise((e) => {
|
|
310
|
-
|
|
441
|
+
r.addEventListener("track", (t) => {
|
|
311
442
|
t.streams && t.streams[0] && e(t.streams[0]);
|
|
312
443
|
});
|
|
313
444
|
});
|
|
314
445
|
}
|
|
315
|
-
const
|
|
446
|
+
const H = [
|
|
316
447
|
{ urls: ["stun:stun.l.google.com:19302"] }
|
|
317
448
|
];
|
|
318
|
-
async function
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
456
|
+
o.addTransceiver("video", { direction: "recvonly" });
|
|
324
457
|
} catch (s) {
|
|
325
458
|
console.warn("[RFWebRTC] Could not add transceiver:", s);
|
|
326
459
|
}
|
|
327
|
-
|
|
460
|
+
r && r.getVideoTracks().forEach((s) => {
|
|
328
461
|
try {
|
|
329
462
|
s.contentHint = "detail";
|
|
330
463
|
} catch {
|
|
331
464
|
}
|
|
332
|
-
|
|
465
|
+
o.addTrack(s, r);
|
|
333
466
|
});
|
|
334
|
-
const
|
|
467
|
+
const n = z(o), i = o.createDataChannel("inference", {
|
|
335
468
|
ordered: !0
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
|
345
|
-
const e =
|
|
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 (
|
|
352
|
-
console.warn("[RFWebRTC] Failed to set encoding parameters:",
|
|
488
|
+
} catch (a) {
|
|
489
|
+
console.warn("[RFWebRTC] Failed to set encoding parameters:", a);
|
|
353
490
|
}
|
|
354
491
|
}
|
|
355
|
-
|
|
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,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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 (
|
|
369
|
-
const { frameId:
|
|
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
|
|
372
|
-
|
|
528
|
+
const g = new TextDecoder("utf-8").decode(w), v = JSON.parse(g);
|
|
529
|
+
l(v);
|
|
373
530
|
}
|
|
374
531
|
} else {
|
|
375
|
-
const
|
|
376
|
-
s
|
|
532
|
+
const s = JSON.parse(c.data);
|
|
533
|
+
l(s);
|
|
377
534
|
}
|
|
378
|
-
} catch (
|
|
379
|
-
console.error("[RFWebRTC] Failed to parse data channel message:",
|
|
535
|
+
} catch (s) {
|
|
536
|
+
console.error("[RFWebRTC] Failed to parse data channel message:", s);
|
|
380
537
|
}
|
|
381
|
-
}), this.dataChannel.addEventListener("error", (
|
|
382
|
-
console.error("[RFWebRTC] Data channel error:",
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
489
|
-
source:
|
|
679
|
+
async function D({
|
|
680
|
+
source: r,
|
|
490
681
|
connector: e,
|
|
491
682
|
wrtcParams: t,
|
|
492
|
-
onData:
|
|
493
|
-
|
|
683
|
+
onData: a,
|
|
684
|
+
onComplete: o,
|
|
685
|
+
onFileUploadProgress: n,
|
|
686
|
+
options: i = {}
|
|
494
687
|
}) {
|
|
495
|
-
var
|
|
688
|
+
var k;
|
|
496
689
|
if (!e || typeof e.connectWrtc != "function")
|
|
497
690
|
throw new Error("connector must have a connectWrtc method");
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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:",
|
|
507
|
-
const
|
|
508
|
-
await
|
|
509
|
-
const
|
|
510
|
-
|
|
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
|
-
|
|
513
|
-
|
|
719
|
+
h.addEventListener("connectionstatechange", _), _(), setTimeout(() => {
|
|
720
|
+
h.removeEventListener("connectionstatechange", _), F(new Error("WebRTC connection timeout after 30s"));
|
|
514
721
|
}, 3e4);
|
|
515
|
-
}),
|
|
516
|
-
const
|
|
517
|
-
|
|
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
|
|
777
|
+
const re = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
520
778
|
__proto__: null,
|
|
521
|
-
ChunkReassembler:
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
787
|
+
b as InferenceHTTPClient,
|
|
788
|
+
ee as connectors,
|
|
789
|
+
te as streams,
|
|
790
|
+
re as webrtc
|
|
531
791
|
};
|
|
792
|
+
//# sourceMappingURL=index.es.js.map
|