@incodetech/core 0.0.0-dev-20260126-4504c5b
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/Manager-Co-PsiG9.d.ts +19 -0
- package/dist/OpenViduLogger-BLxxXoyF.esm.js +803 -0
- package/dist/OpenViduLogger-DyqID_-7.esm.js +3 -0
- package/dist/api-DfRLAneb.esm.js +53 -0
- package/dist/chunk-V5DOKNPJ.esm.js +49 -0
- package/dist/deepsightLoader-BMT0FSg6.esm.js +24 -0
- package/dist/deepsightService-j5zMt6wf.esm.js +236 -0
- package/dist/email.d.ts +264 -0
- package/dist/email.esm.js +478 -0
- package/dist/endpoints-BUsSVoJV.esm.js +3288 -0
- package/dist/events-B8ZkhAZo.esm.js +285 -0
- package/dist/flow.d.ts +278 -0
- package/dist/flow.esm.js +638 -0
- package/dist/getDeviceClass-DkfbtsIJ.esm.js +41 -0
- package/dist/id-r1mw9zBM.esm.js +1827 -0
- package/dist/id.d.ts +5 -0
- package/dist/id.esm.js +9 -0
- package/dist/index-CJMK8K5u.d.ts +614 -0
- package/dist/index.d.ts +445 -0
- package/dist/index.esm.js +163 -0
- package/dist/lib-CbAibJlt.esm.js +11700 -0
- package/dist/phone.d.ts +292 -0
- package/dist/phone.esm.js +552 -0
- package/dist/selfie.d.ts +592 -0
- package/dist/selfie.esm.js +1221 -0
- package/dist/src-DYtpbFY5.esm.js +2781 -0
- package/dist/stats-DnU4uUFv.esm.js +16 -0
- package/dist/stats.d.ts +12 -0
- package/dist/stats.esm.js +4 -0
- package/dist/streamingEvents-CfEJv3xH.esm.js +96 -0
- package/dist/types-CMR6NkxW.d.ts +359 -0
- package/dist/types-CRVSv38Q.d.ts +344 -0
- package/package.json +58 -0
|
@@ -0,0 +1,2781 @@
|
|
|
1
|
+
import { a as __toDynamicImportESM } from "./chunk-V5DOKNPJ.esm.js";
|
|
2
|
+
|
|
3
|
+
//#region ../infra/src/capabilities/platform.ts
|
|
4
|
+
/**
|
|
5
|
+
* Platform detection utilities for camera operations.
|
|
6
|
+
* These are pure functions with no external dependencies and can be used by all layers.
|
|
7
|
+
*/
|
|
8
|
+
let uxType = null;
|
|
9
|
+
function isTouchDevice() {
|
|
10
|
+
return (typeof navigator !== "undefined" ? navigator.maxTouchPoints > 0 : false) || "ontouchstart" in (typeof document !== "undefined" ? document.documentElement : {});
|
|
11
|
+
}
|
|
12
|
+
function isTabletWithoutAndroid(userAgent) {
|
|
13
|
+
const isLinux = /Linux/i.test(userAgent);
|
|
14
|
+
const isChrome = /Chrome/i.test(userAgent) && !/Edge/i.test(userAgent);
|
|
15
|
+
return !/Mobile|Android/i.test(userAgent) && isLinux && isChrome && isTouchDevice();
|
|
16
|
+
}
|
|
17
|
+
function isIOS(userAgent) {
|
|
18
|
+
const ua = userAgent || (typeof navigator !== "undefined" ? navigator.userAgent : "");
|
|
19
|
+
const hasTouchPoints = typeof navigator !== "undefined" ? navigator.maxTouchPoints > 0 : false;
|
|
20
|
+
return /iPad|iPhone|iPod/.test(ua) || /Mac OS/.test(ua) && hasTouchPoints || /iPadOS/.test(ua);
|
|
21
|
+
}
|
|
22
|
+
function isAndroid(userAgent) {
|
|
23
|
+
const ua = userAgent || (typeof navigator !== "undefined" ? navigator.userAgent : "");
|
|
24
|
+
return /Android/i.test(ua);
|
|
25
|
+
}
|
|
26
|
+
function isDesktop(userAgent) {
|
|
27
|
+
if (uxType) return uxType === "desktop";
|
|
28
|
+
const ua = userAgent || (typeof navigator !== "undefined" ? navigator.userAgent : "");
|
|
29
|
+
return !(/Mobile|Android/i.test(ua) || isTabletWithoutAndroid(ua) || isIOS(ua));
|
|
30
|
+
}
|
|
31
|
+
function isSafari(userAgent) {
|
|
32
|
+
const ua = userAgent || (typeof navigator !== "undefined" ? navigator.userAgent : "");
|
|
33
|
+
return /Safari/i.test(ua) && !/Chrome|Chromium|Edge/i.test(ua);
|
|
34
|
+
}
|
|
35
|
+
const IPHONE_14_PLUS_DIMENSIONS = [
|
|
36
|
+
{
|
|
37
|
+
outerHeight: 852,
|
|
38
|
+
outerWidth: 393
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
outerHeight: 932,
|
|
42
|
+
outerWidth: 430
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
innerHeight: 631,
|
|
46
|
+
innerWidth: 375
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
innerHeight: 920,
|
|
50
|
+
innerWidth: 402
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
outerHeight: 874,
|
|
54
|
+
outerWidth: 402
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
innerHeight: 874,
|
|
58
|
+
innerWidth: 402
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
outerHeight: 912,
|
|
62
|
+
outerWidth: 420
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
outerHeight: 873,
|
|
66
|
+
outerWidth: 402
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
outerHeight: 956,
|
|
70
|
+
outerWidth: 440
|
|
71
|
+
}
|
|
72
|
+
];
|
|
73
|
+
function isIPhone14OrHigher() {
|
|
74
|
+
if (typeof window === "undefined") return false;
|
|
75
|
+
const { outerHeight, outerWidth, innerHeight, innerWidth } = window;
|
|
76
|
+
return IPHONE_14_PLUS_DIMENSIONS.some((dims) => dims.outerHeight === outerHeight && dims.outerWidth === outerWidth || dims.innerHeight === innerHeight && dims.innerWidth === innerWidth);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region ../infra/src/http/createApi.ts
|
|
81
|
+
var FetchHttpError = class extends Error {
|
|
82
|
+
constructor(status, statusText, url, method, headers, data) {
|
|
83
|
+
super(`HTTP ${status} ${statusText}`);
|
|
84
|
+
this.ok = false;
|
|
85
|
+
this.status = status;
|
|
86
|
+
this.statusText = statusText;
|
|
87
|
+
this.url = url;
|
|
88
|
+
this.method = method;
|
|
89
|
+
this.headers = headers;
|
|
90
|
+
this.data = data;
|
|
91
|
+
this.name = "FetchHttpError";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const buildQueryString = (query) => {
|
|
95
|
+
if (!query) return "";
|
|
96
|
+
const params = new URLSearchParams();
|
|
97
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
98
|
+
if (value !== void 0 && value !== null) params.append(key, String(value));
|
|
99
|
+
});
|
|
100
|
+
const str = params.toString();
|
|
101
|
+
return str ? `?${str}` : "";
|
|
102
|
+
};
|
|
103
|
+
const buildUrl = (baseURL, url, query) => {
|
|
104
|
+
const queryString = buildQueryString(query);
|
|
105
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return `${url}${queryString}`;
|
|
106
|
+
return `${baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL}${url.startsWith("/") ? url : `/${url}`}${queryString}`;
|
|
107
|
+
};
|
|
108
|
+
const prepareBody = (body) => {
|
|
109
|
+
if (body === null || body === void 0) return null;
|
|
110
|
+
if (body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer) return body;
|
|
111
|
+
if (typeof body === "object") return JSON.stringify(body);
|
|
112
|
+
return String(body);
|
|
113
|
+
};
|
|
114
|
+
const parseResponse = async (response, parseType) => {
|
|
115
|
+
if (parseType === "response") return response;
|
|
116
|
+
if (parseType === "blob") return await response.blob();
|
|
117
|
+
if (parseType === "arrayBuffer") return await response.arrayBuffer();
|
|
118
|
+
if (parseType === "text") return await response.text();
|
|
119
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
120
|
+
if (parseType === "json" || contentType.includes("application/json")) try {
|
|
121
|
+
return await response.json();
|
|
122
|
+
} catch {
|
|
123
|
+
return await response.text();
|
|
124
|
+
}
|
|
125
|
+
return await response.text();
|
|
126
|
+
};
|
|
127
|
+
const DEFAULT_TIMEOUT = 3e4;
|
|
128
|
+
const requestWithXHR = (fullUrl, method, mergedHeaders, requestBody, timeout, signal, onUploadProgress) => {
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const xhr = new XMLHttpRequest();
|
|
131
|
+
xhr.open(method, fullUrl, true);
|
|
132
|
+
Object.entries(mergedHeaders).forEach(([key, value]) => {
|
|
133
|
+
xhr.setRequestHeader(key, value);
|
|
134
|
+
});
|
|
135
|
+
xhr.timeout = timeout;
|
|
136
|
+
xhr.upload.onprogress = (event) => {
|
|
137
|
+
if (event.lengthComputable) {
|
|
138
|
+
const percentComplete = event.loaded / event.total * 100;
|
|
139
|
+
onUploadProgress(Math.round(percentComplete));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
xhr.onload = () => {
|
|
143
|
+
const responseHeaders = {};
|
|
144
|
+
xhr.getAllResponseHeaders().split("\r\n").forEach((line) => {
|
|
145
|
+
const parts = line.split(": ");
|
|
146
|
+
if (parts.length === 2) responseHeaders[parts[0].toLowerCase()] = parts[1];
|
|
147
|
+
});
|
|
148
|
+
let data;
|
|
149
|
+
try {
|
|
150
|
+
if ((xhr.getResponseHeader("content-type") ?? "").includes("application/json")) data = JSON.parse(xhr.responseText);
|
|
151
|
+
else data = xhr.responseText;
|
|
152
|
+
} catch {
|
|
153
|
+
data = xhr.responseText;
|
|
154
|
+
}
|
|
155
|
+
if (xhr.status >= 200 && xhr.status < 300) resolve({
|
|
156
|
+
ok: true,
|
|
157
|
+
status: xhr.status,
|
|
158
|
+
statusText: xhr.statusText,
|
|
159
|
+
url: fullUrl,
|
|
160
|
+
headers: responseHeaders,
|
|
161
|
+
data
|
|
162
|
+
});
|
|
163
|
+
else reject(new FetchHttpError(xhr.status, xhr.statusText, fullUrl, method, responseHeaders, data));
|
|
164
|
+
};
|
|
165
|
+
xhr.onerror = () => {
|
|
166
|
+
reject(new FetchHttpError(0, "Network Error", fullUrl, method, {}, null));
|
|
167
|
+
};
|
|
168
|
+
xhr.ontimeout = () => {
|
|
169
|
+
reject(new FetchHttpError(0, "Request timeout", fullUrl, method, {}, null));
|
|
170
|
+
};
|
|
171
|
+
if (signal) signal.addEventListener("abort", () => {
|
|
172
|
+
xhr.abort();
|
|
173
|
+
reject(new FetchHttpError(0, "Request aborted", fullUrl, method, {}, null));
|
|
174
|
+
});
|
|
175
|
+
xhr.send(requestBody);
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
const createApi = (config) => {
|
|
179
|
+
const headers = {
|
|
180
|
+
"Content-Type": "application/json",
|
|
181
|
+
Accept: "application/json",
|
|
182
|
+
"api-version": "1.0",
|
|
183
|
+
...config.customHeaders ?? {}
|
|
184
|
+
};
|
|
185
|
+
const defaults = {
|
|
186
|
+
baseURL: config.apiURL,
|
|
187
|
+
headers
|
|
188
|
+
};
|
|
189
|
+
const client = {
|
|
190
|
+
defaults,
|
|
191
|
+
async request(requestConfig) {
|
|
192
|
+
const { method = "GET", url, headers: headers$1 = {}, query, params, body, signal, timeout = config.timeout ?? DEFAULT_TIMEOUT, parse, onUploadProgress } = requestConfig;
|
|
193
|
+
const fullUrl = buildUrl(defaults.baseURL, url, params ?? query);
|
|
194
|
+
const mergedHeaders = {
|
|
195
|
+
...defaults.headers,
|
|
196
|
+
...headers$1
|
|
197
|
+
};
|
|
198
|
+
const requestBody = prepareBody(body);
|
|
199
|
+
let finalHeaders = mergedHeaders;
|
|
200
|
+
if (requestBody === null && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
201
|
+
const { "Content-Type": _, ...headersWithoutContentType } = mergedHeaders;
|
|
202
|
+
finalHeaders = headersWithoutContentType;
|
|
203
|
+
}
|
|
204
|
+
if (onUploadProgress) {
|
|
205
|
+
if (requestBody !== null && typeof ReadableStream !== "undefined" && requestBody instanceof ReadableStream) throw new Error("Upload progress tracking is not supported for ReadableStream bodies");
|
|
206
|
+
return requestWithXHR(fullUrl, method, finalHeaders, requestBody, timeout, signal, onUploadProgress);
|
|
207
|
+
}
|
|
208
|
+
const controller = new AbortController();
|
|
209
|
+
const abortSignal = signal ?? controller.signal;
|
|
210
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
211
|
+
try {
|
|
212
|
+
const response = await fetch(fullUrl, {
|
|
213
|
+
method,
|
|
214
|
+
headers: finalHeaders,
|
|
215
|
+
body: requestBody,
|
|
216
|
+
signal: abortSignal
|
|
217
|
+
});
|
|
218
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
219
|
+
const responseHeaders = {};
|
|
220
|
+
response.headers.forEach((value, key) => {
|
|
221
|
+
responseHeaders[key] = value;
|
|
222
|
+
});
|
|
223
|
+
const data = await parseResponse(response, parse);
|
|
224
|
+
if (!response.ok) throw new FetchHttpError(response.status, response.statusText, fullUrl, method, responseHeaders, data);
|
|
225
|
+
return {
|
|
226
|
+
ok: true,
|
|
227
|
+
status: response.status,
|
|
228
|
+
statusText: response.statusText,
|
|
229
|
+
url: fullUrl,
|
|
230
|
+
headers: responseHeaders,
|
|
231
|
+
data
|
|
232
|
+
};
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
235
|
+
if (error instanceof FetchHttpError) throw error;
|
|
236
|
+
if (error instanceof Error && error.name === "AbortError") throw new FetchHttpError(0, "Request timeout", fullUrl, method, {}, null);
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
get(url, config$1) {
|
|
241
|
+
return client.request({
|
|
242
|
+
...config$1,
|
|
243
|
+
url,
|
|
244
|
+
method: "GET"
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
post(url, body, config$1) {
|
|
248
|
+
return client.request({
|
|
249
|
+
...config$1,
|
|
250
|
+
url,
|
|
251
|
+
body,
|
|
252
|
+
method: "POST"
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
put(url, body, config$1) {
|
|
256
|
+
return client.request({
|
|
257
|
+
...config$1,
|
|
258
|
+
url,
|
|
259
|
+
body,
|
|
260
|
+
method: "PUT"
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
patch(url, body, config$1) {
|
|
264
|
+
return client.request({
|
|
265
|
+
...config$1,
|
|
266
|
+
url,
|
|
267
|
+
body,
|
|
268
|
+
method: "PATCH"
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
delete(url, config$1) {
|
|
272
|
+
return client.request({
|
|
273
|
+
...config$1,
|
|
274
|
+
url,
|
|
275
|
+
method: "DELETE"
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
head(url, config$1) {
|
|
279
|
+
return client.request({
|
|
280
|
+
...config$1,
|
|
281
|
+
url,
|
|
282
|
+
method: "HEAD"
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
options(url, config$1) {
|
|
286
|
+
return client.request({
|
|
287
|
+
...config$1,
|
|
288
|
+
url,
|
|
289
|
+
method: "OPTIONS"
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
setHeader(name, value) {
|
|
293
|
+
defaults.headers[name] = value;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
return client;
|
|
297
|
+
};
|
|
298
|
+
var createApi_default = createApi;
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region ../infra/src/manager/Manager.ts
|
|
302
|
+
/**
|
|
303
|
+
* Creates a manager that wraps a state machine actor with a clean public API.
|
|
304
|
+
*
|
|
305
|
+
* The manager provides:
|
|
306
|
+
* - `getState()` - Returns the mapped public state
|
|
307
|
+
* - `subscribe()` - Listen to state changes
|
|
308
|
+
* - `stop()` - Stop the actor
|
|
309
|
+
* - Plus any custom API methods from `createApi`
|
|
310
|
+
*
|
|
311
|
+
* @template TMachine - The state machine type
|
|
312
|
+
* @template TMapState - Function type that maps raw snapshot to public state
|
|
313
|
+
* @template TCreateApi - Function type that creates the custom API
|
|
314
|
+
*
|
|
315
|
+
* @param options - Configuration options
|
|
316
|
+
* @param options.actor - The started actor instance
|
|
317
|
+
* @param options.mapState - Function to transform raw snapshot into public state
|
|
318
|
+
* @param options.createApi - Factory function to create custom API methods
|
|
319
|
+
*
|
|
320
|
+
* @returns Combined manager with base methods and custom API
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* const manager = createManager({
|
|
325
|
+
* actor: createActor(myMachine).start(),
|
|
326
|
+
* mapState: (snapshot) => ({ status: snapshot.value }),
|
|
327
|
+
* createApi: ({ actor }) => ({
|
|
328
|
+
* doSomething() { actor.send({ type: 'DO_IT' }); }
|
|
329
|
+
* }),
|
|
330
|
+
* });
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
function createManager(options) {
|
|
334
|
+
const { actor, mapState, createApi: createApi$1 } = options;
|
|
335
|
+
function getSnapshot() {
|
|
336
|
+
return actor.getSnapshot();
|
|
337
|
+
}
|
|
338
|
+
function getState() {
|
|
339
|
+
return mapState(getSnapshot());
|
|
340
|
+
}
|
|
341
|
+
function subscribe(listener) {
|
|
342
|
+
const subscription = actor.subscribe(() => {
|
|
343
|
+
listener(getState());
|
|
344
|
+
});
|
|
345
|
+
return () => subscription.unsubscribe();
|
|
346
|
+
}
|
|
347
|
+
const api = createApi$1({
|
|
348
|
+
actor,
|
|
349
|
+
getSnapshot
|
|
350
|
+
});
|
|
351
|
+
const base = {
|
|
352
|
+
getState,
|
|
353
|
+
subscribe,
|
|
354
|
+
stop() {
|
|
355
|
+
actor.stop();
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
return Object.defineProperties(base, Object.getOwnPropertyDescriptors(api));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region ../infra/src/media/camera.ts
|
|
363
|
+
/**
|
|
364
|
+
* Request camera access with specific constraints.
|
|
365
|
+
* Throws if the request fails - no automatic fallback.
|
|
366
|
+
*/
|
|
367
|
+
async function requestCameraAccess(constraints) {
|
|
368
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices) throw new Error("MediaDevices API not available");
|
|
369
|
+
return navigator.mediaDevices.getUserMedia(constraints);
|
|
370
|
+
}
|
|
371
|
+
function stopCameraStream(stream) {
|
|
372
|
+
for (const track of stream.getTracks()) track.stop();
|
|
373
|
+
}
|
|
374
|
+
async function enumerateVideoDevices() {
|
|
375
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices) return [];
|
|
376
|
+
return (await navigator.mediaDevices.enumerateDevices()).filter((d) => d.kind === "videoinput");
|
|
377
|
+
}
|
|
378
|
+
async function applyTrackConstraints(track, constraints) {
|
|
379
|
+
await track.applyConstraints(constraints);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region ../infra/src/media/canvas.ts
|
|
384
|
+
/**
|
|
385
|
+
* Class representing a canvas element for image capture and manipulation.
|
|
386
|
+
*/
|
|
387
|
+
var IncodeCanvas = class IncodeCanvas {
|
|
388
|
+
/**
|
|
389
|
+
* Creates an {@link IncodeCanvas} from a raw {@link ImageData} frame.
|
|
390
|
+
* @param imageData - Frame pixels in RGBA format
|
|
391
|
+
* @returns An {@link IncodeCanvas} containing the provided pixels
|
|
392
|
+
*/
|
|
393
|
+
static fromImageData(imageData) {
|
|
394
|
+
const canvas = document.createElement("canvas");
|
|
395
|
+
canvas.width = imageData.width;
|
|
396
|
+
canvas.height = imageData.height;
|
|
397
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
398
|
+
if (!context) return new IncodeCanvas(canvas);
|
|
399
|
+
if ("putImageData" in context) context.putImageData(imageData, 0, 0);
|
|
400
|
+
return new IncodeCanvas(canvas);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Create a new canvas element.
|
|
404
|
+
* @param canvas_ - The canvas element to clone.
|
|
405
|
+
*/
|
|
406
|
+
constructor(canvas_) {
|
|
407
|
+
this.base64Image = null;
|
|
408
|
+
this.blobData = null;
|
|
409
|
+
this.canvas = document.createElement("canvas");
|
|
410
|
+
this.canvas.width = canvas_.width;
|
|
411
|
+
this.canvas.height = canvas_.height;
|
|
412
|
+
const context = this.canvas.getContext("2d");
|
|
413
|
+
if (context) context.drawImage(canvas_, 0, 0);
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Check if the current canvas is valid.
|
|
417
|
+
*/
|
|
418
|
+
checkCanvas() {
|
|
419
|
+
return this.canvas && this.canvas.width > 1 && this.canvas.height > 1;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Disposes of resources, including revoking object URLs to prevent memory leaks.
|
|
423
|
+
*/
|
|
424
|
+
dispose() {
|
|
425
|
+
if (this.blobData?.url) {
|
|
426
|
+
URL.revokeObjectURL(this.blobData.url);
|
|
427
|
+
this.blobData = null;
|
|
428
|
+
}
|
|
429
|
+
this.base64Image = null;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Release the data stored by IncodeCanvas.
|
|
433
|
+
*/
|
|
434
|
+
release() {
|
|
435
|
+
if (!this.checkCanvas()) return;
|
|
436
|
+
this.canvas.width = 1;
|
|
437
|
+
this.canvas.height = 1;
|
|
438
|
+
this.canvas.getContext("2d")?.clearRect(0, 0, 1, 1);
|
|
439
|
+
this.base64Image = null;
|
|
440
|
+
if (this.blobData?.url) URL.revokeObjectURL(this.blobData.url);
|
|
441
|
+
this.blobData = null;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Revokes the object URL if one exists, preventing memory leaks.
|
|
445
|
+
* Use this when you no longer need the preview image URL.
|
|
446
|
+
*/
|
|
447
|
+
revokeObjectURL() {
|
|
448
|
+
if (this.blobData?.url) {
|
|
449
|
+
URL.revokeObjectURL(this.blobData.url);
|
|
450
|
+
this.blobData = {
|
|
451
|
+
...this.blobData,
|
|
452
|
+
url: ""
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Get the width of the canvas.
|
|
458
|
+
*/
|
|
459
|
+
width() {
|
|
460
|
+
if (!this.checkCanvas()) return null;
|
|
461
|
+
return this.canvas.width;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Get the height of the canvas.
|
|
465
|
+
*/
|
|
466
|
+
height() {
|
|
467
|
+
if (!this.checkCanvas()) return null;
|
|
468
|
+
return this.canvas.height;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Set the width of the canvas.
|
|
472
|
+
*/
|
|
473
|
+
setWidth(width) {
|
|
474
|
+
if (!this.checkCanvas()) return;
|
|
475
|
+
this.canvas.width = width;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Set the height of the canvas.
|
|
479
|
+
*/
|
|
480
|
+
setHeight(height) {
|
|
481
|
+
if (!this.checkCanvas()) return;
|
|
482
|
+
this.canvas.height = height;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Clone the current canvas.
|
|
486
|
+
*/
|
|
487
|
+
clone() {
|
|
488
|
+
if (!this.checkCanvas()) return null;
|
|
489
|
+
const cloned = new IncodeCanvas(this.canvas);
|
|
490
|
+
cloned.setBase64Image(this.base64Image);
|
|
491
|
+
return cloned;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Deep clone the current IncodeCanvas including blob data.
|
|
495
|
+
*/
|
|
496
|
+
async deepClone() {
|
|
497
|
+
const cloned = this.clone();
|
|
498
|
+
if (!cloned) return null;
|
|
499
|
+
if (this.blobData) await cloned.setBlobData(this.blobData);
|
|
500
|
+
return cloned;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Returns the drawing context on the canvas.
|
|
504
|
+
*/
|
|
505
|
+
getContext(contextId, contextAttributes) {
|
|
506
|
+
if (!this.checkCanvas()) return null;
|
|
507
|
+
const context = this.canvas.getContext(contextId, contextAttributes);
|
|
508
|
+
return context instanceof CanvasRenderingContext2D ? context : null;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Retrieves the image data from the canvas.
|
|
512
|
+
*/
|
|
513
|
+
getImageData() {
|
|
514
|
+
if (!this.checkCanvas()) return null;
|
|
515
|
+
const context = this.canvas.getContext("2d", { willReadFrequently: true });
|
|
516
|
+
if (!context) return null;
|
|
517
|
+
return context.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Updates the base64 representation of the current canvas image.
|
|
521
|
+
*/
|
|
522
|
+
updateBase64Image(jpegQuality = 1) {
|
|
523
|
+
if (!this.checkCanvas()) return;
|
|
524
|
+
this.base64Image = this.canvas.toDataURL("image/jpeg", jpegQuality);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Converts the current canvas element to a base64 string.
|
|
528
|
+
*/
|
|
529
|
+
getBase64Image(jpegQuality = 1, includeDataURLPrefix = false) {
|
|
530
|
+
if (!this.checkCanvas()) return null;
|
|
531
|
+
if (this.base64Image === null) this.updateBase64Image(jpegQuality);
|
|
532
|
+
const base64Image = this.base64Image;
|
|
533
|
+
if (base64Image === null) return null;
|
|
534
|
+
if (includeDataURLPrefix) return base64Image;
|
|
535
|
+
const commaIndex = base64Image.indexOf(",");
|
|
536
|
+
if (commaIndex === -1) return base64Image;
|
|
537
|
+
return base64Image.slice(commaIndex + 1);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Sets the base64 representation of the current canvas image.
|
|
541
|
+
*/
|
|
542
|
+
setBase64Image(base64Image) {
|
|
543
|
+
this.base64Image = base64Image;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Updates the Blob representation of the current canvas image.
|
|
547
|
+
*/
|
|
548
|
+
updateBlob(jpegQuality = 1, includeDataURLPrefix = false) {
|
|
549
|
+
if (!this.checkCanvas()) return;
|
|
550
|
+
if (this.base64Image === null) this.updateBase64Image(jpegQuality);
|
|
551
|
+
const base64 = this.getBase64Image(jpegQuality, includeDataURLPrefix);
|
|
552
|
+
if (base64) this.blobData = IncodeCanvas.base64ToBlob(base64);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Converts a base64 string to a Blob and creates a URL for it.
|
|
556
|
+
*/
|
|
557
|
+
static base64ToBlob(base64) {
|
|
558
|
+
try {
|
|
559
|
+
const binary = atob(base64);
|
|
560
|
+
const array = [];
|
|
561
|
+
for (let i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i));
|
|
562
|
+
const blob = new Blob([new Uint8Array(array)]);
|
|
563
|
+
return {
|
|
564
|
+
blob,
|
|
565
|
+
url: URL.createObjectURL(blob)
|
|
566
|
+
};
|
|
567
|
+
} catch (error) {
|
|
568
|
+
console.error("Failed to convert base64 string to Blob:", error);
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Retrieves the Blob data and its URL from the current canvas.
|
|
574
|
+
*/
|
|
575
|
+
getBlobData(jpegQuality = 1, includeDataURLPrefix = false) {
|
|
576
|
+
if (!this.checkCanvas()) return null;
|
|
577
|
+
if (this.blobData === null) this.updateBlob(jpegQuality, includeDataURLPrefix);
|
|
578
|
+
return this.blobData;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Sets the Blob data of the current canvas image.
|
|
582
|
+
*/
|
|
583
|
+
async setBlobData(blobData) {
|
|
584
|
+
const blobDataArrayBuffer = await blobData.blob.arrayBuffer();
|
|
585
|
+
this.blobData = {
|
|
586
|
+
blob: new Blob([blobDataArrayBuffer], { type: blobData.blob.type }),
|
|
587
|
+
url: blobData.url
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Returns a resized canvas according to video element size.
|
|
592
|
+
*/
|
|
593
|
+
getResizedCanvas(videoElementWidth, videoElementHeight) {
|
|
594
|
+
if (!this.checkCanvas()) return null;
|
|
595
|
+
const cameraOffsetX = Math.abs(videoElementWidth - window.innerWidth);
|
|
596
|
+
const resized = new IncodeCanvas(document.createElement("canvas"));
|
|
597
|
+
const canvasHeight = this.height();
|
|
598
|
+
if (!canvasHeight) return null;
|
|
599
|
+
const ratio = canvasHeight / videoElementHeight;
|
|
600
|
+
const newWidth = window.innerWidth;
|
|
601
|
+
const newHeight = window.innerHeight;
|
|
602
|
+
resized.setWidth(newWidth);
|
|
603
|
+
resized.setHeight(newHeight);
|
|
604
|
+
const context = resized.getContext("2d");
|
|
605
|
+
if (!context) {
|
|
606
|
+
console.error("Failed to get 2d context in getResizedCanvas method.");
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
context.drawImage(this.canvas, ratio * cameraOffsetX / 2, 0, ratio * newWidth, ratio * newHeight, 0, 0, newWidth, newHeight);
|
|
610
|
+
return resized;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region ../infra/src/media/video.ts
|
|
616
|
+
/**
|
|
617
|
+
* Creates a hidden video element for a stream and appends it to document.body.
|
|
618
|
+
*/
|
|
619
|
+
function createHiddenVideoElement(stream) {
|
|
620
|
+
if (typeof document === "undefined") throw new Error("Document not available");
|
|
621
|
+
const video = document.createElement("video");
|
|
622
|
+
video.autoplay = true;
|
|
623
|
+
video.playsInline = true;
|
|
624
|
+
video.muted = true;
|
|
625
|
+
video.srcObject = stream;
|
|
626
|
+
video.style.width = "0px";
|
|
627
|
+
video.style.height = "0px";
|
|
628
|
+
video.style.position = "absolute";
|
|
629
|
+
video.style.top = "0";
|
|
630
|
+
video.style.left = "0";
|
|
631
|
+
video.style.zIndex = "-1";
|
|
632
|
+
document.body.appendChild(video);
|
|
633
|
+
return {
|
|
634
|
+
element: video,
|
|
635
|
+
dispose: () => {
|
|
636
|
+
video.srcObject = null;
|
|
637
|
+
if (video.parentElement) video.parentElement.removeChild(video);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
//#endregion
|
|
643
|
+
//#region ../infra/src/media/permissions.ts
|
|
644
|
+
async function queryPermission(name) {
|
|
645
|
+
try {
|
|
646
|
+
if (typeof navigator === "undefined" || !navigator.permissions) return "prompt";
|
|
647
|
+
return (await navigator.permissions.query({ name })).state;
|
|
648
|
+
} catch {
|
|
649
|
+
return "prompt";
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async function queryCameraPermission() {
|
|
653
|
+
return queryPermission("camera");
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
//#endregion
|
|
657
|
+
//#region ../infra/src/media/StreamCanvasCapture.ts
|
|
658
|
+
var StreamCanvasCapture = class {
|
|
659
|
+
constructor(stream, options) {
|
|
660
|
+
this.hasFrame = false;
|
|
661
|
+
this.disposed = false;
|
|
662
|
+
this.eventTarget = new EventTarget();
|
|
663
|
+
this.video = document.createElement("video");
|
|
664
|
+
this.video.srcObject = stream;
|
|
665
|
+
this.video.autoplay = true;
|
|
666
|
+
this.video.playsInline = true;
|
|
667
|
+
this.video.muted = true;
|
|
668
|
+
const settings = stream.getVideoTracks()[0]?.getSettings();
|
|
669
|
+
const initialWidth = options?.width ?? settings?.width ?? 1280;
|
|
670
|
+
const initialHeight = options?.height ?? settings?.height ?? 720;
|
|
671
|
+
this.canvas = document.createElement("canvas");
|
|
672
|
+
this.canvas.width = initialWidth;
|
|
673
|
+
this.canvas.height = initialHeight;
|
|
674
|
+
this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
|
|
675
|
+
const fps = options?.fps ?? 10;
|
|
676
|
+
const intervalMs = fps > 0 ? Math.max(16, Math.floor(1e3 / fps)) : 0;
|
|
677
|
+
this.video.addEventListener("loadedmetadata", () => {
|
|
678
|
+
if (this.video.videoWidth > 0 && this.video.videoHeight > 0) {
|
|
679
|
+
this.canvas.width = this.video.videoWidth;
|
|
680
|
+
this.canvas.height = this.video.videoHeight;
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
try {
|
|
684
|
+
this.video.play();
|
|
685
|
+
} catch {}
|
|
686
|
+
this.rafLoop(intervalMs);
|
|
687
|
+
}
|
|
688
|
+
addEventListener(type, listener, options) {
|
|
689
|
+
this.eventTarget.addEventListener(type, listener, options);
|
|
690
|
+
}
|
|
691
|
+
removeEventListener(type, listener, options) {
|
|
692
|
+
this.eventTarget.removeEventListener(type, listener, options);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Returns the latest cached frame as an {@link IncodeCanvas}.
|
|
696
|
+
*/
|
|
697
|
+
getLatestCanvas() {
|
|
698
|
+
if (!this.hasFrame) this.tick();
|
|
699
|
+
if (!this.hasFrame) return null;
|
|
700
|
+
return new IncodeCanvas(this.canvas);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Returns the latest cached frame as raw {@link ImageData}.
|
|
704
|
+
*/
|
|
705
|
+
getLatestFrame() {
|
|
706
|
+
if (!this.ctx) return null;
|
|
707
|
+
if (!this.hasFrame) this.tick();
|
|
708
|
+
if (!this.hasFrame) return null;
|
|
709
|
+
try {
|
|
710
|
+
return this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
|
711
|
+
} catch {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Disposes internal resources and stops the capture loop.
|
|
717
|
+
*/
|
|
718
|
+
dispose() {
|
|
719
|
+
if (this.disposed) return;
|
|
720
|
+
this.disposed = true;
|
|
721
|
+
if (this.rafId !== void 0) {
|
|
722
|
+
window.cancelAnimationFrame(this.rafId);
|
|
723
|
+
this.rafId = void 0;
|
|
724
|
+
}
|
|
725
|
+
this.video.srcObject = null;
|
|
726
|
+
this.canvas.width = 0;
|
|
727
|
+
this.canvas.height = 0;
|
|
728
|
+
this.hasFrame = false;
|
|
729
|
+
}
|
|
730
|
+
rafLoop(intervalMs) {
|
|
731
|
+
const loop = (timeMs) => {
|
|
732
|
+
if (this.disposed) return;
|
|
733
|
+
if (intervalMs <= 0 || this.lastTickTimeMs === void 0 || timeMs - this.lastTickTimeMs >= intervalMs) {
|
|
734
|
+
this.lastTickTimeMs = timeMs;
|
|
735
|
+
const previousFrameTimeSeconds = this.lastFrameTimeSeconds;
|
|
736
|
+
this.tick();
|
|
737
|
+
const currentFrameTimeSeconds = this.video.currentTime;
|
|
738
|
+
if (previousFrameTimeSeconds === void 0) {
|
|
739
|
+
if (this.hasFrame) {
|
|
740
|
+
this.lastFrameTimeSeconds = currentFrameTimeSeconds;
|
|
741
|
+
this.eventTarget.dispatchEvent(new Event("frame"));
|
|
742
|
+
}
|
|
743
|
+
} else if (this.hasFrame && currentFrameTimeSeconds !== previousFrameTimeSeconds) {
|
|
744
|
+
this.lastFrameTimeSeconds = currentFrameTimeSeconds;
|
|
745
|
+
this.eventTarget.dispatchEvent(new Event("frame"));
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
this.rafId = window.requestAnimationFrame(loop);
|
|
749
|
+
};
|
|
750
|
+
this.rafId = window.requestAnimationFrame(loop);
|
|
751
|
+
}
|
|
752
|
+
tick() {
|
|
753
|
+
if (!this.ctx) return;
|
|
754
|
+
if (this.video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) return;
|
|
755
|
+
const videoWidth = this.video.videoWidth;
|
|
756
|
+
const videoHeight = this.video.videoHeight;
|
|
757
|
+
if (videoWidth === 0 || videoHeight === 0) return;
|
|
758
|
+
if (this.canvas.width !== videoWidth || this.canvas.height !== videoHeight) {
|
|
759
|
+
this.canvas.width = videoWidth;
|
|
760
|
+
this.canvas.height = videoHeight;
|
|
761
|
+
}
|
|
762
|
+
try {
|
|
763
|
+
this.ctx.drawImage(this.video, 0, 0);
|
|
764
|
+
this.hasFrame = true;
|
|
765
|
+
} catch {
|
|
766
|
+
this.hasFrame = false;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region ../infra/src/media/StreamCanvasProcessingSession.ts
|
|
773
|
+
var StreamCanvasProcessingSession = class {
|
|
774
|
+
/**
|
|
775
|
+
* Creates a processing session that reacts to `StreamCanvasCapture` frame events
|
|
776
|
+
* and drives a provider's `processFrame()` method with backpressure.
|
|
777
|
+
*/
|
|
778
|
+
constructor(params) {
|
|
779
|
+
this.disposed = false;
|
|
780
|
+
this.isProcessing = false;
|
|
781
|
+
this.onFrameEvent = () => {
|
|
782
|
+
if (this.disposed || this.isProcessing) return;
|
|
783
|
+
const frame = this.capturer.getLatestFrame();
|
|
784
|
+
if (!frame) return;
|
|
785
|
+
this.onFrame?.(frame);
|
|
786
|
+
this.isProcessing = true;
|
|
787
|
+
this.provider.processFrame(frame).catch(() => {}).finally(() => {
|
|
788
|
+
this.isProcessing = false;
|
|
789
|
+
});
|
|
790
|
+
};
|
|
791
|
+
this.capturer = params.capturer;
|
|
792
|
+
this.provider = params.provider;
|
|
793
|
+
this.onFrame = params.onFrame;
|
|
794
|
+
this.capturer.addEventListener("frame", this.onFrameEvent);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Unsubscribes from frame events and resets the provider.
|
|
798
|
+
*/
|
|
799
|
+
dispose() {
|
|
800
|
+
if (this.disposed) return;
|
|
801
|
+
this.disposed = true;
|
|
802
|
+
this.capturer.removeEventListener("frame", this.onFrameEvent);
|
|
803
|
+
this.provider.reset();
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Returns whether the session has been disposed.
|
|
807
|
+
*/
|
|
808
|
+
isDisposed() {
|
|
809
|
+
return this.disposed;
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
//#endregion
|
|
814
|
+
//#region ../infra/src/media/VideoTrimmer.ts
|
|
815
|
+
async function getDurationBySeek(videoBlob) {
|
|
816
|
+
const video = document.createElement("video");
|
|
817
|
+
video.preload = "metadata";
|
|
818
|
+
video.src = URL.createObjectURL(videoBlob);
|
|
819
|
+
try {
|
|
820
|
+
if (!Number.isFinite(video.duration)) {
|
|
821
|
+
video.currentTime = Number.MAX_SAFE_INTEGER;
|
|
822
|
+
await new Promise((resolve) => {
|
|
823
|
+
const onDurationChange = () => {
|
|
824
|
+
if (Number.isFinite(video.duration)) {
|
|
825
|
+
video.removeEventListener("durationchange", onDurationChange);
|
|
826
|
+
video.removeEventListener("timeupdate", onTimeUpdate);
|
|
827
|
+
resolve(video.duration);
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
const onTimeUpdate = () => {
|
|
831
|
+
if (Number.isFinite(video.duration)) {
|
|
832
|
+
video.removeEventListener("timeupdate", onTimeUpdate);
|
|
833
|
+
video.removeEventListener("durationchange", onDurationChange);
|
|
834
|
+
resolve(video.duration);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
video.addEventListener("durationchange", onDurationChange);
|
|
838
|
+
video.addEventListener("timeupdate", onTimeUpdate);
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
const duration = video.duration;
|
|
842
|
+
return Number.isFinite(duration) ? duration : null;
|
|
843
|
+
} finally {
|
|
844
|
+
URL.revokeObjectURL(video.src);
|
|
845
|
+
video.src = "";
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
async function trimLastNSecondsUsingPlayback(videoBlob, seconds) {
|
|
849
|
+
const video = document.createElement("video");
|
|
850
|
+
video.preload = "metadata";
|
|
851
|
+
video.playsInline = true;
|
|
852
|
+
video.muted = true;
|
|
853
|
+
const videoURL = URL.createObjectURL(videoBlob);
|
|
854
|
+
video.src = videoURL;
|
|
855
|
+
const duration = await getDurationBySeek(videoBlob);
|
|
856
|
+
if (!duration || duration < seconds) {
|
|
857
|
+
URL.revokeObjectURL(videoURL);
|
|
858
|
+
return videoBlob;
|
|
859
|
+
}
|
|
860
|
+
const startTime = Math.max(0, Math.floor(duration) - seconds);
|
|
861
|
+
await new Promise((resolve) => {
|
|
862
|
+
if (video.readyState >= 2) resolve();
|
|
863
|
+
else video.addEventListener("loadedmetadata", () => resolve(), { once: true });
|
|
864
|
+
});
|
|
865
|
+
const canvas = document.createElement("canvas");
|
|
866
|
+
canvas.width = 230;
|
|
867
|
+
canvas.height = 320;
|
|
868
|
+
const captureFps = isAndroid() ? 15 : 24;
|
|
869
|
+
const stream = canvas.captureStream(captureFps);
|
|
870
|
+
const ctx = canvas.getContext("2d");
|
|
871
|
+
video.currentTime = startTime;
|
|
872
|
+
await new Promise((resolve) => {
|
|
873
|
+
video.addEventListener("seeked", () => resolve(), { once: true });
|
|
874
|
+
});
|
|
875
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
876
|
+
const mimeType = videoBlob.type || (MediaRecorder.isTypeSupported("video/webm") ? "video/webm" : "video/mp4");
|
|
877
|
+
const mediaRecorder = new MediaRecorder(stream.clone(), {
|
|
878
|
+
mimeType,
|
|
879
|
+
videoBitsPerSecond: 5e5,
|
|
880
|
+
bitsPerSecond: 5e5
|
|
881
|
+
});
|
|
882
|
+
const chunks = [];
|
|
883
|
+
mediaRecorder.ondataavailable = (event) => {
|
|
884
|
+
if (event.data.size > 0) chunks.push(event.data);
|
|
885
|
+
};
|
|
886
|
+
const recordingPromise = new Promise((resolve) => {
|
|
887
|
+
mediaRecorder.onstop = () => {
|
|
888
|
+
const trimmedBlob$1 = new Blob(chunks, { type: mimeType });
|
|
889
|
+
URL.revokeObjectURL(videoURL);
|
|
890
|
+
mediaRecorder.stream?.getTracks().forEach((track) => track.stop());
|
|
891
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
892
|
+
video.src = "";
|
|
893
|
+
resolve(trimmedBlob$1);
|
|
894
|
+
};
|
|
895
|
+
});
|
|
896
|
+
video.addEventListener("play", () => {
|
|
897
|
+
function drawVideo() {
|
|
898
|
+
if (video.currentTime >= duration) {
|
|
899
|
+
mediaRecorder.stop();
|
|
900
|
+
video.pause();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
904
|
+
requestAnimationFrame(drawVideo);
|
|
905
|
+
}
|
|
906
|
+
drawVideo();
|
|
907
|
+
setTimeout(() => {
|
|
908
|
+
mediaRecorder.start(100);
|
|
909
|
+
}, 500);
|
|
910
|
+
});
|
|
911
|
+
video.play().catch(() => {
|
|
912
|
+
URL.revokeObjectURL(videoURL);
|
|
913
|
+
});
|
|
914
|
+
const trimmedBlob = await recordingPromise;
|
|
915
|
+
console.timeEnd("trimLastNSecondsUsingPlayback");
|
|
916
|
+
return trimmedBlob;
|
|
917
|
+
}
|
|
918
|
+
async function trimLastNSeconds(videoBlob, seconds) {
|
|
919
|
+
return await trimLastNSecondsUsingPlayback(videoBlob, seconds);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
//#endregion
|
|
923
|
+
//#region ../infra/src/providers/browser/BrowserEnvironmentProvider.ts
|
|
924
|
+
var BrowserEnvironmentProvider = class {
|
|
925
|
+
getScreenDimensions() {
|
|
926
|
+
if (typeof screen === "undefined") return {
|
|
927
|
+
width: 0,
|
|
928
|
+
height: 0
|
|
929
|
+
};
|
|
930
|
+
return {
|
|
931
|
+
width: screen.width,
|
|
932
|
+
height: screen.height
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
generateCanvasFingerprint() {
|
|
936
|
+
try {
|
|
937
|
+
if (typeof document === "undefined") return "";
|
|
938
|
+
const canvas = document.createElement("canvas");
|
|
939
|
+
const ctx = canvas.getContext("2d");
|
|
940
|
+
if (!ctx) return "";
|
|
941
|
+
ctx.textBaseline = "top";
|
|
942
|
+
ctx.font = "14px Arial";
|
|
943
|
+
ctx.fillText("fingerprint", 2, 2);
|
|
944
|
+
return canvas.toDataURL().slice(-50);
|
|
945
|
+
} catch {
|
|
946
|
+
return "";
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
getWebGLRenderer() {
|
|
950
|
+
try {
|
|
951
|
+
if (typeof document === "undefined") return "";
|
|
952
|
+
const canvas = document.createElement("canvas");
|
|
953
|
+
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
954
|
+
if (!gl) return "";
|
|
955
|
+
const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
956
|
+
if (!debugInfo) return "";
|
|
957
|
+
return gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) || "";
|
|
958
|
+
} catch {
|
|
959
|
+
return "";
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
getWindowProperty(prop) {
|
|
963
|
+
if (typeof window === "undefined") return;
|
|
964
|
+
return window[prop];
|
|
965
|
+
}
|
|
966
|
+
getNavigatorProperty(prop) {
|
|
967
|
+
if (typeof navigator === "undefined") return;
|
|
968
|
+
return navigator[prop];
|
|
969
|
+
}
|
|
970
|
+
getNavigatorPrefixes() {
|
|
971
|
+
if (typeof navigator === "undefined") return {
|
|
972
|
+
webkit: false,
|
|
973
|
+
moz: false,
|
|
974
|
+
o: false,
|
|
975
|
+
ms: false
|
|
976
|
+
};
|
|
977
|
+
const nav = navigator;
|
|
978
|
+
return {
|
|
979
|
+
webkit: nav.webkitGetUserMedia !== void 0,
|
|
980
|
+
moz: nav.mozGetUserMedia !== void 0,
|
|
981
|
+
o: nav.oGetUserMedia !== void 0,
|
|
982
|
+
ms: nav.msGetUserMedia !== void 0
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
async fetchJson(url, timeoutMs = 3e3) {
|
|
986
|
+
try {
|
|
987
|
+
if (typeof fetch === "undefined") return null;
|
|
988
|
+
const controller = new AbortController();
|
|
989
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
990
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
991
|
+
clearTimeout(timeoutId);
|
|
992
|
+
return await response.json();
|
|
993
|
+
} catch {
|
|
994
|
+
return null;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
async enumerateVideoDeviceLabels() {
|
|
998
|
+
try {
|
|
999
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.enumerateDevices) return [];
|
|
1000
|
+
return (await navigator.mediaDevices.enumerateDevices()).filter((device) => device.kind === "videoinput").map((device) => device.label);
|
|
1001
|
+
} catch {
|
|
1002
|
+
return [];
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
getTimestamp() {
|
|
1006
|
+
return Date.now();
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
//#endregion
|
|
1011
|
+
//#region ../infra/src/providers/browser/BrowserStorageProvider.ts
|
|
1012
|
+
/**
|
|
1013
|
+
* Browser-based storage provider wrapping localStorage.
|
|
1014
|
+
* Handles JSON serialization/deserialization automatically.
|
|
1015
|
+
*/
|
|
1016
|
+
var BrowserStorageProvider = class {
|
|
1017
|
+
/**
|
|
1018
|
+
* Retrieves a value from localStorage.
|
|
1019
|
+
* @param key - The storage key
|
|
1020
|
+
* @returns The stored value or null if not found
|
|
1021
|
+
* @throws Error if stored value is invalid JSON
|
|
1022
|
+
*/
|
|
1023
|
+
async get(key) {
|
|
1024
|
+
try {
|
|
1025
|
+
const item = localStorage.getItem(key);
|
|
1026
|
+
if (item === null) return null;
|
|
1027
|
+
return JSON.parse(item);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (error instanceof SyntaxError) throw new Error(`Invalid JSON stored at key "${key}": ${error.message}`);
|
|
1030
|
+
throw error;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Stores a value in localStorage.
|
|
1035
|
+
* @param key - The storage key
|
|
1036
|
+
* @param value - The value to store (will be serialized as JSON)
|
|
1037
|
+
* @throws Error if value cannot be serialized or quota is exceeded
|
|
1038
|
+
*/
|
|
1039
|
+
async set(key, value) {
|
|
1040
|
+
try {
|
|
1041
|
+
const serialized = JSON.stringify(value);
|
|
1042
|
+
localStorage.setItem(key, serialized);
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
if ((error instanceof DOMException || error.name === "QuotaExceededError") && error.name === "QuotaExceededError") throw new Error(`Storage quota exceeded for key "${key}"`);
|
|
1045
|
+
if (error instanceof TypeError) throw new Error(`Failed to serialize value for key "${key}": ${error.message}`);
|
|
1046
|
+
throw error;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Removes a value from localStorage.
|
|
1051
|
+
* @param key - The storage key to remove
|
|
1052
|
+
*/
|
|
1053
|
+
async remove(key) {
|
|
1054
|
+
localStorage.removeItem(key);
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Clears all values from localStorage.
|
|
1058
|
+
*/
|
|
1059
|
+
async clear() {
|
|
1060
|
+
localStorage.clear();
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
//#endregion
|
|
1065
|
+
//#region ../infra/src/providers/browser/BrowserTimerProvider.ts
|
|
1066
|
+
var BrowserTimerProvider = class BrowserTimerProvider {
|
|
1067
|
+
static getInstance() {
|
|
1068
|
+
if (!BrowserTimerProvider.instance) BrowserTimerProvider.instance = new BrowserTimerProvider();
|
|
1069
|
+
return BrowserTimerProvider.instance;
|
|
1070
|
+
}
|
|
1071
|
+
setTimeout(callback, delayMs) {
|
|
1072
|
+
return setTimeout(callback, delayMs);
|
|
1073
|
+
}
|
|
1074
|
+
setInterval(callback, delayMs) {
|
|
1075
|
+
return setInterval(callback, delayMs);
|
|
1076
|
+
}
|
|
1077
|
+
clearTimeout(handle) {
|
|
1078
|
+
clearTimeout(handle);
|
|
1079
|
+
}
|
|
1080
|
+
clearInterval(handle) {
|
|
1081
|
+
clearInterval(handle);
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
//#endregion
|
|
1086
|
+
//#region ../infra/src/providers/browser/LocalRecordingProvider.ts
|
|
1087
|
+
function toBase64(videoBlob) {
|
|
1088
|
+
return new Promise((resolve, reject) => {
|
|
1089
|
+
const reader = new FileReader();
|
|
1090
|
+
reader.onloadend = () => {
|
|
1091
|
+
const base64 = reader.result.split(",")[1];
|
|
1092
|
+
resolve(base64);
|
|
1093
|
+
};
|
|
1094
|
+
reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("FileReader error"));
|
|
1095
|
+
reader.readAsDataURL(videoBlob);
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
function getSupportedMediaRecorderMimeType() {
|
|
1099
|
+
const possibleTypes = isIOS() ? [
|
|
1100
|
+
"video/mp4",
|
|
1101
|
+
"video/webm",
|
|
1102
|
+
"video/webm;codecs=vp9",
|
|
1103
|
+
"video/webm;codecs=vp8"
|
|
1104
|
+
] : [
|
|
1105
|
+
"video/webm",
|
|
1106
|
+
"video/webm;codecs=vp9",
|
|
1107
|
+
"video/webm;codecs=vp8",
|
|
1108
|
+
"video/mp4"
|
|
1109
|
+
];
|
|
1110
|
+
for (const type of possibleTypes) if (MediaRecorder.isTypeSupported(type)) return type;
|
|
1111
|
+
return "";
|
|
1112
|
+
}
|
|
1113
|
+
function getAdaptiveMediaRecorderOptions() {
|
|
1114
|
+
const mimeType = getSupportedMediaRecorderMimeType();
|
|
1115
|
+
if (isIOS()) return {
|
|
1116
|
+
mimeType,
|
|
1117
|
+
videoBitsPerSecond: 1e6,
|
|
1118
|
+
bitsPerSecond: 1e6
|
|
1119
|
+
};
|
|
1120
|
+
return {
|
|
1121
|
+
mimeType,
|
|
1122
|
+
videoBitsPerSecond: 5e5,
|
|
1123
|
+
bitsPerSecond: 5e5
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
var LocalRecordingProvider = class {
|
|
1127
|
+
constructor() {
|
|
1128
|
+
this.mediaRecorder = null;
|
|
1129
|
+
this._isRecording = false;
|
|
1130
|
+
this._hasError = false;
|
|
1131
|
+
this._error = null;
|
|
1132
|
+
this.mimeType = "";
|
|
1133
|
+
this.stream = null;
|
|
1134
|
+
this.pauseRecordingBound = this.pauseRecording.bind(this);
|
|
1135
|
+
}
|
|
1136
|
+
get isRecording() {
|
|
1137
|
+
return this._isRecording;
|
|
1138
|
+
}
|
|
1139
|
+
get hasError() {
|
|
1140
|
+
return this._hasError;
|
|
1141
|
+
}
|
|
1142
|
+
get error() {
|
|
1143
|
+
return this._error;
|
|
1144
|
+
}
|
|
1145
|
+
startRecording(stream) {
|
|
1146
|
+
this.reset();
|
|
1147
|
+
this.registerEventListeners();
|
|
1148
|
+
this.stream = stream;
|
|
1149
|
+
try {
|
|
1150
|
+
const options = getAdaptiveMediaRecorderOptions();
|
|
1151
|
+
this.mimeType = options.mimeType;
|
|
1152
|
+
const recorder = new MediaRecorder(stream.clone(), options);
|
|
1153
|
+
recorder.onerror = (event) => {
|
|
1154
|
+
this._error = `Recording error: ${event}`;
|
|
1155
|
+
this._isRecording = false;
|
|
1156
|
+
this._hasError = true;
|
|
1157
|
+
};
|
|
1158
|
+
recorder.start();
|
|
1159
|
+
this.mediaRecorder = recorder;
|
|
1160
|
+
this._isRecording = true;
|
|
1161
|
+
this._error = null;
|
|
1162
|
+
this._hasError = false;
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
this._error = `Failed to start recording: ${err}`;
|
|
1165
|
+
this._hasError = true;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
async stopRecording(trimSeconds, encrypt, generateChecksum) {
|
|
1169
|
+
const recorder = this.mediaRecorder;
|
|
1170
|
+
return new Promise((resolve, reject) => {
|
|
1171
|
+
this.removeEventListeners();
|
|
1172
|
+
if (recorder && this._isRecording) {
|
|
1173
|
+
const chunks = [];
|
|
1174
|
+
recorder.ondataavailable = (event) => {
|
|
1175
|
+
if (event.data.size > 0) chunks.push(event.data);
|
|
1176
|
+
};
|
|
1177
|
+
recorder.onstop = async () => {
|
|
1178
|
+
try {
|
|
1179
|
+
const trimmedVideo = await trimLastNSeconds(new Blob(chunks, { type: this.mimeType }), trimSeconds);
|
|
1180
|
+
const encryptedVideoBase64 = encrypt(await toBase64(trimmedVideo));
|
|
1181
|
+
generateChecksum(await trimmedVideo.arrayBuffer());
|
|
1182
|
+
this._isRecording = false;
|
|
1183
|
+
resolve({
|
|
1184
|
+
trimmedBlob: trimmedVideo,
|
|
1185
|
+
encryptedVideo: encryptedVideoBase64
|
|
1186
|
+
});
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
this._isRecording = false;
|
|
1189
|
+
this._error = `Recording stop failed: ${error}`;
|
|
1190
|
+
this._hasError = true;
|
|
1191
|
+
reject(error);
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
recorder.stop();
|
|
1195
|
+
this._isRecording = false;
|
|
1196
|
+
} else resolve({
|
|
1197
|
+
trimmedBlob: new Blob([], { type: this.mimeType }),
|
|
1198
|
+
encryptedVideo: ""
|
|
1199
|
+
});
|
|
1200
|
+
recorder?.stream?.getTracks().forEach((track) => track.stop());
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
reset() {
|
|
1204
|
+
this._isRecording = false;
|
|
1205
|
+
this._error = null;
|
|
1206
|
+
this._hasError = false;
|
|
1207
|
+
}
|
|
1208
|
+
pauseRecording() {
|
|
1209
|
+
if (!this._isRecording) return;
|
|
1210
|
+
if (this.mediaRecorder?.state === "recording") try {
|
|
1211
|
+
this.mediaRecorder.pause();
|
|
1212
|
+
} catch {}
|
|
1213
|
+
}
|
|
1214
|
+
registerEventListeners() {
|
|
1215
|
+
document.addEventListener("visibilitychange", this.pauseRecordingBound);
|
|
1216
|
+
}
|
|
1217
|
+
removeEventListeners() {
|
|
1218
|
+
document.removeEventListener("visibilitychange", this.pauseRecordingBound);
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
//#endregion
|
|
1223
|
+
//#region ../infra/src/providers/browser/MotionSensorProvider.ts
|
|
1224
|
+
var MotionSensorProvider = class {
|
|
1225
|
+
constructor(thresholds) {
|
|
1226
|
+
this._isRunning = false;
|
|
1227
|
+
this._hasPermission = true;
|
|
1228
|
+
this.acl = null;
|
|
1229
|
+
this.gyro = null;
|
|
1230
|
+
this.minNumberOfFrames = 3;
|
|
1231
|
+
this.maxFrequency = 60;
|
|
1232
|
+
this.emptyParams = {
|
|
1233
|
+
meanX: 0,
|
|
1234
|
+
meanY: 0,
|
|
1235
|
+
meanZ: 0,
|
|
1236
|
+
m2X: 0,
|
|
1237
|
+
m2Y: 0,
|
|
1238
|
+
m2Z: 0,
|
|
1239
|
+
cumulativeAbsErrorX: 0,
|
|
1240
|
+
cumulativeAbsErrorY: 0,
|
|
1241
|
+
cumulativeAbsErrorZ: 0,
|
|
1242
|
+
ptsNum: 0
|
|
1243
|
+
};
|
|
1244
|
+
this.paramsAcc = { ...this.emptyParams };
|
|
1245
|
+
this.paramsGyro = { ...this.emptyParams };
|
|
1246
|
+
this.paramsAccGrOld = { ...this.emptyParams };
|
|
1247
|
+
this.paramsAccOld = { ...this.emptyParams };
|
|
1248
|
+
this.paramsGyroscopeOld = { ...this.emptyParams };
|
|
1249
|
+
this.paramsOrientationOld = { ...this.emptyParams };
|
|
1250
|
+
this.deviceMotionListener = (event) => {
|
|
1251
|
+
if (event.accelerationIncludingGravity) {
|
|
1252
|
+
const accGrVal = {
|
|
1253
|
+
x: event.accelerationIncludingGravity.x,
|
|
1254
|
+
y: event.accelerationIncludingGravity.y,
|
|
1255
|
+
z: event.accelerationIncludingGravity.z
|
|
1256
|
+
};
|
|
1257
|
+
if (this.checkFields(accGrVal)) {
|
|
1258
|
+
this.paramsAccGrOld.ptsNum += 1;
|
|
1259
|
+
this.updateParams(this.paramsAccGrOld, accGrVal);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
if (event.acceleration) {
|
|
1263
|
+
const accVal = {
|
|
1264
|
+
x: event.acceleration.x,
|
|
1265
|
+
y: event.acceleration.y,
|
|
1266
|
+
z: event.acceleration.z
|
|
1267
|
+
};
|
|
1268
|
+
if (this.checkFields(accVal)) {
|
|
1269
|
+
this.paramsAccOld.ptsNum += 1;
|
|
1270
|
+
this.updateParams(this.paramsAccOld, accVal);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (event.rotationRate) {
|
|
1274
|
+
const gyroVal = {
|
|
1275
|
+
x: event.rotationRate.alpha,
|
|
1276
|
+
y: event.rotationRate.beta,
|
|
1277
|
+
z: event.rotationRate.gamma
|
|
1278
|
+
};
|
|
1279
|
+
if (this.checkFields(gyroVal)) {
|
|
1280
|
+
this.paramsGyroscopeOld.ptsNum += 1;
|
|
1281
|
+
this.updateParams(this.paramsGyroscopeOld, gyroVal);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
this.deviceOrientationListener = (event) => {
|
|
1286
|
+
const orientationVal = {
|
|
1287
|
+
x: event.alpha,
|
|
1288
|
+
y: event.beta,
|
|
1289
|
+
z: event.gamma
|
|
1290
|
+
};
|
|
1291
|
+
if (this.checkFields(orientationVal)) {
|
|
1292
|
+
this.paramsOrientationOld.ptsNum += 1;
|
|
1293
|
+
this.updateParams(this.paramsOrientationOld, orientationVal);
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
this.thresholds = {
|
|
1297
|
+
accThreshold: thresholds?.accThreshold ?? .3,
|
|
1298
|
+
gyroThreshold: thresholds?.gyroThreshold ?? .3,
|
|
1299
|
+
accGrOldThreshold: thresholds?.accGrOldThreshold ?? .3,
|
|
1300
|
+
accOldThreshold: thresholds?.accOldThreshold ?? .3,
|
|
1301
|
+
gyroscopeOldThreshold: thresholds?.gyroscopeOldThreshold ?? 10,
|
|
1302
|
+
orientationOldThreshold: thresholds?.orientationOldThreshold ?? 10,
|
|
1303
|
+
maeAccThreshold: thresholds?.maeAccThreshold ?? .3,
|
|
1304
|
+
maeGyroThreshold: thresholds?.maeGyroThreshold ?? .3,
|
|
1305
|
+
maeAccGrOldThreshold: thresholds?.maeAccGrOldThreshold ?? .3,
|
|
1306
|
+
maeAccOldThreshold: thresholds?.maeAccOldThreshold ?? .3,
|
|
1307
|
+
maeGyroscopeOldThreshold: thresholds?.maeGyroscopeOldThreshold ?? 10,
|
|
1308
|
+
maeOrientationOldThreshold: thresholds?.maeOrientationOldThreshold ?? 10
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
get isRunning() {
|
|
1312
|
+
return this._isRunning;
|
|
1313
|
+
}
|
|
1314
|
+
get hasPermission() {
|
|
1315
|
+
return this._hasPermission;
|
|
1316
|
+
}
|
|
1317
|
+
async requestPermission() {
|
|
1318
|
+
if (typeof DeviceMotionEvent !== "undefined" && typeof DeviceMotionEvent.requestPermission === "function") {
|
|
1319
|
+
sessionStorage.setItem("motionSensorsPermissionsRequested", String(true));
|
|
1320
|
+
try {
|
|
1321
|
+
if (await DeviceMotionEvent.requestPermission() !== "granted") {
|
|
1322
|
+
this._hasPermission = false;
|
|
1323
|
+
return "denied";
|
|
1324
|
+
}
|
|
1325
|
+
return "granted";
|
|
1326
|
+
} catch {
|
|
1327
|
+
this._hasPermission = false;
|
|
1328
|
+
return "denied";
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return "not-required";
|
|
1332
|
+
}
|
|
1333
|
+
initializeAccelerometer() {
|
|
1334
|
+
if (!("Accelerometer" in window)) return;
|
|
1335
|
+
try {
|
|
1336
|
+
const AccelerometerConstructor = window.Accelerometer;
|
|
1337
|
+
if (AccelerometerConstructor) {
|
|
1338
|
+
this.acl = new AccelerometerConstructor({ frequency: this.maxFrequency });
|
|
1339
|
+
this.acl.addEventListener("reading", () => this.updateAcc());
|
|
1340
|
+
}
|
|
1341
|
+
} catch {}
|
|
1342
|
+
}
|
|
1343
|
+
initializeGyroscope() {
|
|
1344
|
+
if (!("Gyroscope" in window)) return;
|
|
1345
|
+
try {
|
|
1346
|
+
const GyroscopeConstructor = window.Gyroscope;
|
|
1347
|
+
if (GyroscopeConstructor) {
|
|
1348
|
+
this.gyro = new GyroscopeConstructor({ frequency: this.maxFrequency });
|
|
1349
|
+
this.gyro.addEventListener("reading", () => this.updateGyro());
|
|
1350
|
+
}
|
|
1351
|
+
} catch {}
|
|
1352
|
+
}
|
|
1353
|
+
async start() {
|
|
1354
|
+
if (this._isRunning || !this._hasPermission) return;
|
|
1355
|
+
try {
|
|
1356
|
+
this.initializeAccelerometer();
|
|
1357
|
+
if (this.acl) this.acl.start();
|
|
1358
|
+
} catch {}
|
|
1359
|
+
try {
|
|
1360
|
+
this.initializeGyroscope();
|
|
1361
|
+
if (this.gyro) this.gyro.start();
|
|
1362
|
+
} catch {}
|
|
1363
|
+
window.addEventListener("devicemotion", this.deviceMotionListener);
|
|
1364
|
+
window.addEventListener("deviceorientation", this.deviceOrientationListener);
|
|
1365
|
+
this._isRunning = true;
|
|
1366
|
+
}
|
|
1367
|
+
stop() {
|
|
1368
|
+
this._isRunning = false;
|
|
1369
|
+
if (this.acl) this.acl.stop();
|
|
1370
|
+
if (this.gyro) this.gyro.stop();
|
|
1371
|
+
window.removeEventListener("devicemotion", this.deviceMotionListener);
|
|
1372
|
+
window.removeEventListener("deviceorientation", this.deviceOrientationListener);
|
|
1373
|
+
this.paramsAcc = { ...this.emptyParams };
|
|
1374
|
+
this.paramsGyro = { ...this.emptyParams };
|
|
1375
|
+
this.paramsAccGrOld = { ...this.emptyParams };
|
|
1376
|
+
this.paramsAccOld = { ...this.emptyParams };
|
|
1377
|
+
this.paramsGyroscopeOld = { ...this.emptyParams };
|
|
1378
|
+
this.paramsOrientationOld = { ...this.emptyParams };
|
|
1379
|
+
}
|
|
1380
|
+
updateAcc() {
|
|
1381
|
+
if (!this._isRunning || !this.acl) return;
|
|
1382
|
+
this.paramsAcc.ptsNum += 1;
|
|
1383
|
+
const val = {
|
|
1384
|
+
x: this.acl.x,
|
|
1385
|
+
y: this.acl.y,
|
|
1386
|
+
z: this.acl.z
|
|
1387
|
+
};
|
|
1388
|
+
if (this.checkFields(val)) this.updateParams(this.paramsAcc, val);
|
|
1389
|
+
}
|
|
1390
|
+
updateGyro() {
|
|
1391
|
+
if (!this._isRunning || !this.gyro) return;
|
|
1392
|
+
this.paramsGyro.ptsNum += 1;
|
|
1393
|
+
const val = {
|
|
1394
|
+
x: this.gyro.x,
|
|
1395
|
+
y: this.gyro.y,
|
|
1396
|
+
z: this.gyro.z
|
|
1397
|
+
};
|
|
1398
|
+
if (this.checkFields(val)) this.updateParams(this.paramsGyro, val);
|
|
1399
|
+
}
|
|
1400
|
+
check() {
|
|
1401
|
+
if (!this._hasPermission) return "UNCLEAR";
|
|
1402
|
+
const result = this.checkStability();
|
|
1403
|
+
return result === null ? "UNCLEAR" : result ? "FAIL" : "PASS";
|
|
1404
|
+
}
|
|
1405
|
+
checkStability() {
|
|
1406
|
+
if (!this._isRunning) return null;
|
|
1407
|
+
const filteredStdChecks = [
|
|
1408
|
+
this.gyro ? this.isBelowStdThreshold(this.paramsGyro, this.thresholds.gyroThreshold) : null,
|
|
1409
|
+
this.acl ? this.isBelowStdThreshold(this.paramsAcc, this.thresholds.accThreshold) : null,
|
|
1410
|
+
this.isBelowStdThreshold(this.paramsAccGrOld, this.thresholds.accGrOldThreshold),
|
|
1411
|
+
this.isBelowStdThreshold(this.paramsAccOld, this.thresholds.accOldThreshold),
|
|
1412
|
+
this.isBelowStdThreshold(this.paramsGyroscopeOld, this.thresholds.gyroscopeOldThreshold),
|
|
1413
|
+
this.isBelowStdThreshold(this.paramsOrientationOld, this.thresholds.orientationOldThreshold)
|
|
1414
|
+
].filter((check) => check !== null);
|
|
1415
|
+
const finalStdCheck = filteredStdChecks.length > 0 ? filteredStdChecks.every((check) => check) : null;
|
|
1416
|
+
const filteredMaeChecks = [
|
|
1417
|
+
this.gyro ? this.isBelowMaeThreshold(this.paramsGyro, this.thresholds.maeGyroThreshold) : null,
|
|
1418
|
+
this.acl ? this.isBelowMaeThreshold(this.paramsAcc, this.thresholds.maeAccThreshold) : null,
|
|
1419
|
+
this.isBelowMaeThreshold(this.paramsAccGrOld, this.thresholds.maeAccGrOldThreshold),
|
|
1420
|
+
this.isBelowMaeThreshold(this.paramsAccOld, this.thresholds.maeAccOldThreshold),
|
|
1421
|
+
this.isBelowMaeThreshold(this.paramsGyroscopeOld, this.thresholds.maeGyroscopeOldThreshold),
|
|
1422
|
+
this.isBelowMaeThreshold(this.paramsOrientationOld, this.thresholds.maeOrientationOldThreshold)
|
|
1423
|
+
].filter((check) => check !== null);
|
|
1424
|
+
const finalMaeCheck = filteredMaeChecks.length > 0 ? filteredMaeChecks.every((check) => check) : null;
|
|
1425
|
+
return finalStdCheck === null ? finalMaeCheck === null ? null : finalMaeCheck : finalMaeCheck !== null ? finalStdCheck && finalMaeCheck : finalStdCheck;
|
|
1426
|
+
}
|
|
1427
|
+
updateParams(params, val) {
|
|
1428
|
+
const deltaX = val.x - params.meanX;
|
|
1429
|
+
const deltaY = val.y - params.meanY;
|
|
1430
|
+
const deltaZ = val.z - params.meanZ;
|
|
1431
|
+
params.meanX += deltaX / params.ptsNum;
|
|
1432
|
+
params.meanY += deltaY / params.ptsNum;
|
|
1433
|
+
params.meanZ += deltaZ / params.ptsNum;
|
|
1434
|
+
params.m2X += deltaX * (val.x - params.meanX);
|
|
1435
|
+
params.m2Y += deltaY * (val.y - params.meanY);
|
|
1436
|
+
params.m2Z += deltaZ * (val.z - params.meanZ);
|
|
1437
|
+
params.cumulativeAbsErrorX += Math.abs(val.x - params.meanX);
|
|
1438
|
+
params.cumulativeAbsErrorY += Math.abs(val.y - params.meanY);
|
|
1439
|
+
params.cumulativeAbsErrorZ += Math.abs(val.z - params.meanZ);
|
|
1440
|
+
}
|
|
1441
|
+
calculateStd(params) {
|
|
1442
|
+
const variance = {
|
|
1443
|
+
x: params.m2X / params.ptsNum,
|
|
1444
|
+
y: params.m2Y / params.ptsNum,
|
|
1445
|
+
z: params.m2Z / params.ptsNum
|
|
1446
|
+
};
|
|
1447
|
+
return {
|
|
1448
|
+
x: Math.sqrt(variance.x),
|
|
1449
|
+
y: Math.sqrt(variance.y),
|
|
1450
|
+
z: Math.sqrt(variance.z)
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
calculateMae(params) {
|
|
1454
|
+
return {
|
|
1455
|
+
x: params.cumulativeAbsErrorX / params.ptsNum,
|
|
1456
|
+
y: params.cumulativeAbsErrorY / params.ptsNum,
|
|
1457
|
+
z: params.cumulativeAbsErrorZ / params.ptsNum
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
checkFields(point) {
|
|
1461
|
+
return point.x !== null && point.y !== null && point.z !== null;
|
|
1462
|
+
}
|
|
1463
|
+
isBelowStdThreshold(params, threshold) {
|
|
1464
|
+
if (params.ptsNum < this.minNumberOfFrames) return null;
|
|
1465
|
+
const std = this.calculateStd(params);
|
|
1466
|
+
return std.x < threshold && std.y < threshold && std.z < threshold;
|
|
1467
|
+
}
|
|
1468
|
+
isBelowMaeThreshold(params, threshold) {
|
|
1469
|
+
if (params.ptsNum < this.minNumberOfFrames) return null;
|
|
1470
|
+
const mae = this.calculateMae(params);
|
|
1471
|
+
return mae.x < threshold && mae.y < threshold && mae.z < threshold;
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
//#endregion
|
|
1476
|
+
//#region ../infra/src/providers/browser/openviduLazy.ts
|
|
1477
|
+
let openViduImport;
|
|
1478
|
+
async function enableProdModeBeforeLoad() {
|
|
1479
|
+
const { OpenViduLogger } = await import("./OpenViduLogger-DyqID_-7.esm.js").then(__toDynamicImportESM(1));
|
|
1480
|
+
OpenViduLogger.getInstance().enableProdMode();
|
|
1481
|
+
}
|
|
1482
|
+
async function loadOpenVidu() {
|
|
1483
|
+
if (openViduImport) return openViduImport;
|
|
1484
|
+
await enableProdModeBeforeLoad();
|
|
1485
|
+
openViduImport = import("./lib-CbAibJlt.esm.js").then(__toDynamicImportESM(1));
|
|
1486
|
+
return openViduImport;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
//#endregion
|
|
1490
|
+
//#region ../infra/src/providers/browser/OpenViduRecordingProvider.ts
|
|
1491
|
+
function createHiddenContainer() {
|
|
1492
|
+
const id = `inc_rec_${Math.random().toString(36).slice(2)}`;
|
|
1493
|
+
const existing = document.getElementById(id);
|
|
1494
|
+
if (existing) return {
|
|
1495
|
+
id,
|
|
1496
|
+
element: existing
|
|
1497
|
+
};
|
|
1498
|
+
const div = document.createElement("div");
|
|
1499
|
+
div.id = id;
|
|
1500
|
+
div.style.display = "none";
|
|
1501
|
+
document.body.appendChild(div);
|
|
1502
|
+
return {
|
|
1503
|
+
id,
|
|
1504
|
+
element: div
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
var OpenViduRecordingProvider = class {
|
|
1508
|
+
async connect(params) {
|
|
1509
|
+
const { OpenVidu } = await loadOpenVidu();
|
|
1510
|
+
const container = createHiddenContainer();
|
|
1511
|
+
const ov = new OpenVidu();
|
|
1512
|
+
const session = ov.initSession();
|
|
1513
|
+
const videoTrack = params.stream.getVideoTracks()[0];
|
|
1514
|
+
const audioTrack = params.stream.getAudioTracks()[0];
|
|
1515
|
+
const hasAudio = audioTrack !== void 0;
|
|
1516
|
+
const publisher = ov.initPublisher(container.id, {
|
|
1517
|
+
publishAudio: hasAudio,
|
|
1518
|
+
audioSource: hasAudio ? audioTrack : false,
|
|
1519
|
+
videoSource: videoTrack ?? false,
|
|
1520
|
+
frameRate: 30
|
|
1521
|
+
});
|
|
1522
|
+
session.on("exception", (event) => {
|
|
1523
|
+
const e = event;
|
|
1524
|
+
params.events?.onSessionException?.({
|
|
1525
|
+
name: e?.name,
|
|
1526
|
+
message: e?.message,
|
|
1527
|
+
sessionId: session.sessionId
|
|
1528
|
+
});
|
|
1529
|
+
});
|
|
1530
|
+
publisher.on("streamCreated", () => {
|
|
1531
|
+
params.events?.onPublisherCreated?.({
|
|
1532
|
+
sessionId: session.sessionId,
|
|
1533
|
+
streamId: publisher.stream?.streamId
|
|
1534
|
+
});
|
|
1535
|
+
});
|
|
1536
|
+
await session.connect(params.sessionToken);
|
|
1537
|
+
params.events?.onSessionConnected?.(session.sessionId);
|
|
1538
|
+
try {
|
|
1539
|
+
await session.publish(publisher);
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1542
|
+
params.events?.onPublisherError?.({
|
|
1543
|
+
message,
|
|
1544
|
+
sessionId: session.sessionId,
|
|
1545
|
+
streamId: publisher.stream?.streamId
|
|
1546
|
+
});
|
|
1547
|
+
throw error;
|
|
1548
|
+
}
|
|
1549
|
+
return {
|
|
1550
|
+
sessionId: session.sessionId,
|
|
1551
|
+
publisher: {
|
|
1552
|
+
getStreamId: () => publisher.stream?.streamId,
|
|
1553
|
+
replaceVideoTrack: async (track) => {
|
|
1554
|
+
await publisher.replaceTrack(track);
|
|
1555
|
+
},
|
|
1556
|
+
destroy: () => {}
|
|
1557
|
+
},
|
|
1558
|
+
disconnect: async () => {
|
|
1559
|
+
try {
|
|
1560
|
+
session.disconnect();
|
|
1561
|
+
params.stream.getTracks().forEach((t) => {
|
|
1562
|
+
t.stop();
|
|
1563
|
+
});
|
|
1564
|
+
} finally {
|
|
1565
|
+
params.events?.onSessionDisconnected?.(session.sessionId);
|
|
1566
|
+
if (container.element.parentNode) container.element.parentNode.removeChild(container.element);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1573
|
+
//#endregion
|
|
1574
|
+
//#region ../infra/src/providers/browser/VisibilityProvider.ts
|
|
1575
|
+
var VisibilityProvider = class {
|
|
1576
|
+
constructor() {
|
|
1577
|
+
this._wasBackgrounded = false;
|
|
1578
|
+
this.visibilityChangeHandler = this.onVisibilityChange.bind(this);
|
|
1579
|
+
document.addEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
1580
|
+
}
|
|
1581
|
+
get wasBackgrounded() {
|
|
1582
|
+
return this._wasBackgrounded;
|
|
1583
|
+
}
|
|
1584
|
+
reset() {
|
|
1585
|
+
this._wasBackgrounded = false;
|
|
1586
|
+
}
|
|
1587
|
+
cleanup() {
|
|
1588
|
+
document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
|
|
1589
|
+
}
|
|
1590
|
+
onVisibilityChange() {
|
|
1591
|
+
if (document.hidden) this._wasBackgrounded = true;
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
//#endregion
|
|
1596
|
+
//#region ../infra/src/wasm/IdCaptureModelType.ts
|
|
1597
|
+
let IdCaptureModelType = /* @__PURE__ */ function(IdCaptureModelType$1) {
|
|
1598
|
+
IdCaptureModelType$1[IdCaptureModelType$1["IdCaptureV1x"] = 0] = "IdCaptureV1x";
|
|
1599
|
+
IdCaptureModelType$1[IdCaptureModelType$1["IdCaptureV2x"] = 1] = "IdCaptureV2x";
|
|
1600
|
+
IdCaptureModelType$1[IdCaptureModelType$1["IdCaptureV3x"] = 2] = "IdCaptureV3x";
|
|
1601
|
+
return IdCaptureModelType$1;
|
|
1602
|
+
}({});
|
|
1603
|
+
|
|
1604
|
+
//#endregion
|
|
1605
|
+
//#region ../infra/src/wasm/Mutex.ts
|
|
1606
|
+
var Semaphore = class {
|
|
1607
|
+
constructor(maxConcurrent = 1) {
|
|
1608
|
+
this.maxConcurrent = maxConcurrent;
|
|
1609
|
+
this.current = 0;
|
|
1610
|
+
this.queue = [];
|
|
1611
|
+
}
|
|
1612
|
+
async acquire() {
|
|
1613
|
+
if (this.current < this.maxConcurrent) {
|
|
1614
|
+
this.current++;
|
|
1615
|
+
return Promise.resolve();
|
|
1616
|
+
}
|
|
1617
|
+
return new Promise((resolve) => {
|
|
1618
|
+
this.queue.push(resolve);
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
release() {
|
|
1622
|
+
this.current = Math.max(0, this.current - 1);
|
|
1623
|
+
if (this.queue.length > 0 && this.current < this.maxConcurrent) {
|
|
1624
|
+
this.current++;
|
|
1625
|
+
const next = this.queue.shift();
|
|
1626
|
+
if (next) next();
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1630
|
+
var Mutex = class {
|
|
1631
|
+
constructor() {
|
|
1632
|
+
this.semaphore = new Semaphore(1);
|
|
1633
|
+
}
|
|
1634
|
+
async lock() {
|
|
1635
|
+
return this.semaphore.acquire();
|
|
1636
|
+
}
|
|
1637
|
+
unlock() {
|
|
1638
|
+
this.semaphore.release();
|
|
1639
|
+
}
|
|
1640
|
+
async withLock(fn, ...args) {
|
|
1641
|
+
await this.lock();
|
|
1642
|
+
try {
|
|
1643
|
+
return await fn(...args);
|
|
1644
|
+
} finally {
|
|
1645
|
+
this.unlock();
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
|
|
1650
|
+
//#endregion
|
|
1651
|
+
//#region ../infra/src/wasm/WasmPipelineType.ts
|
|
1652
|
+
let WasmPipelineType = /* @__PURE__ */ function(WasmPipelineType$1) {
|
|
1653
|
+
WasmPipelineType$1[WasmPipelineType$1["IdBlurGlarePipeline"] = 0] = "IdBlurGlarePipeline";
|
|
1654
|
+
WasmPipelineType$1[WasmPipelineType$1["IdBarcodeAndTextQualityPipeline"] = 1] = "IdBarcodeAndTextQualityPipeline";
|
|
1655
|
+
WasmPipelineType$1[WasmPipelineType$1["IdVideoSelfiePipeline"] = 2] = "IdVideoSelfiePipeline";
|
|
1656
|
+
WasmPipelineType$1[WasmPipelineType$1["SelfieWithAggregationMetrics"] = 3] = "SelfieWithAggregationMetrics";
|
|
1657
|
+
WasmPipelineType$1[WasmPipelineType$1["SelfieWithQualityMetrics"] = 4] = "SelfieWithQualityMetrics";
|
|
1658
|
+
WasmPipelineType$1[WasmPipelineType$1["IdFaceDetectionPipeline"] = 5] = "IdFaceDetectionPipeline";
|
|
1659
|
+
return WasmPipelineType$1;
|
|
1660
|
+
}({});
|
|
1661
|
+
|
|
1662
|
+
//#endregion
|
|
1663
|
+
//#region ../infra/src/wasm/mlWasmJSApi.ts
|
|
1664
|
+
window.wasmArrayBuffer = null;
|
|
1665
|
+
var MlWasmJSApi = class MlWasmJSApi {
|
|
1666
|
+
constructor() {
|
|
1667
|
+
this.versionsFile = null;
|
|
1668
|
+
this.modelsBuffers = null;
|
|
1669
|
+
this.inputImageBuffer = null;
|
|
1670
|
+
this.wasmModule = null;
|
|
1671
|
+
this.pipelineApiUtilities = null;
|
|
1672
|
+
this.utilityApi = null;
|
|
1673
|
+
this.idCaptureWasmApi = null;
|
|
1674
|
+
this.faceDetectionWasmApi = null;
|
|
1675
|
+
this.imageWidth_ = null;
|
|
1676
|
+
this.imageHeight_ = null;
|
|
1677
|
+
this.pipelines_ = null;
|
|
1678
|
+
this.isInitialized_ = false;
|
|
1679
|
+
this.inspectorOpened_ = false;
|
|
1680
|
+
this.wasmCallMutex = new Mutex();
|
|
1681
|
+
this.Module = null;
|
|
1682
|
+
this.loadModule = async (glueCodePath) => {
|
|
1683
|
+
this.Module = (await import(
|
|
1684
|
+
/* @vite-ignore */
|
|
1685
|
+
/* webpackIgnore: true */
|
|
1686
|
+
glueCodePath
|
|
1687
|
+
)).Module;
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Initiallization method, use it when the application starts
|
|
1692
|
+
* TODO: this method can return the list of promises to be awaited together with other initialization code
|
|
1693
|
+
* @param {string} webLibPath - The path to the webassembly binary
|
|
1694
|
+
* @param {string} webLibPathSimd - The path to the SIMD version webassembly binary
|
|
1695
|
+
* @param {boolean} useSimd - Indicates whether SIMD optimizations should be used
|
|
1696
|
+
* @param {string} versionsFile - The path to the file containing versioning information
|
|
1697
|
+
* @param {Map.<WasmPipelineType, Array<string>>} pipelines - pipelines for initialization and corresponding models paths
|
|
1698
|
+
* @returns {Promise<boolean>} A promise that resolves to a boolean indicating whether SIMD is enabled once the initialization is complete.
|
|
1699
|
+
* @throws {Error} Throws an error if the initialization fails.
|
|
1700
|
+
*/
|
|
1701
|
+
async initialize(webLibPath, webLibPathSimd, glueCodePath, useSimd, versionsFile, pipelines) {
|
|
1702
|
+
await this.freeResources();
|
|
1703
|
+
this.pipelines_ = pipelines;
|
|
1704
|
+
this.versionsFile = versionsFile;
|
|
1705
|
+
await this.loadModule(glueCodePath);
|
|
1706
|
+
const simdIsEnabled = await this.loadWasm(useSimd, webLibPath, webLibPathSimd);
|
|
1707
|
+
this.pipelineApiUtilities = new this.wasmModule.PipelineApiUtilities();
|
|
1708
|
+
this.utilityApi = new this.wasmModule.UtilityApi();
|
|
1709
|
+
this.idCaptureWasmApi = new this.wasmModule.IdCaptureApi();
|
|
1710
|
+
this.faceDetectionWasmApi = new this.wasmModule.FaceDetectionApi();
|
|
1711
|
+
this.isInitialized_ = true;
|
|
1712
|
+
return simdIsEnabled;
|
|
1713
|
+
}
|
|
1714
|
+
static getInstance() {
|
|
1715
|
+
if (!MlWasmJSApi.instance) MlWasmJSApi.instance = new MlWasmJSApi();
|
|
1716
|
+
return MlWasmJSApi.instance;
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Returns initialization status
|
|
1720
|
+
* @returns
|
|
1721
|
+
*/
|
|
1722
|
+
isInitialized() {
|
|
1723
|
+
return this.pipelineApiUtilities != null && this.utilityApi != null && this.idCaptureWasmApi != null && this.faceDetectionWasmApi != null && this.isInitialized_;
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Allocate shared memory for image buffers (RGBA format)
|
|
1727
|
+
* Use it every time when the frame dimentions changes
|
|
1728
|
+
* @param {number} imageWidth - image width
|
|
1729
|
+
* @param {number} imageHeight - image height
|
|
1730
|
+
*/
|
|
1731
|
+
async allocateImageBuffers(imageWidth, imageHeight) {
|
|
1732
|
+
this.checkWasmInitialization("Unable to allocate image buffers, cpp API hasn't been initialized");
|
|
1733
|
+
if (imageWidth && imageHeight && imageWidth == this.imageWidth_ && imageHeight == this.imageHeight_ && this.inputImageBuffer && imageWidth * imageHeight * 4 === this.inputImageBuffer.length) return;
|
|
1734
|
+
this.imageWidth_ = imageWidth;
|
|
1735
|
+
this.imageHeight_ = imageHeight;
|
|
1736
|
+
this.inputImageBuffer = this.pipelineApiUtilities.allocateInputImageBuffer(imageWidth, imageHeight, 4);
|
|
1737
|
+
}
|
|
1738
|
+
async handleDetectionCallAndUpdateState(type) {
|
|
1739
|
+
this.checkWasmInitialization("Unable to update pipeline state, cpp API hasn't been initialized");
|
|
1740
|
+
this.pipelineApiUtilities.handleDetectionCallAndUpdateState(this.pipelineTypeToWasmEnum(type));
|
|
1741
|
+
}
|
|
1742
|
+
ens(image) {
|
|
1743
|
+
this.checkWasmInitialization("Unable to encrypt the image, cpp API hasn't been initialized");
|
|
1744
|
+
return this.utilityApi.ens(image);
|
|
1745
|
+
}
|
|
1746
|
+
isVirtualCamera(label) {
|
|
1747
|
+
this.checkWasmInitialization("Unable to check if the camera is virtual, cpp API hasn't been initialized");
|
|
1748
|
+
if (label) return this.utilityApi.isVirtualCamera(label);
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
estimatePerformance() {
|
|
1752
|
+
this.checkWasmInitialization("Unable to estimate performance, cpp API hasn't been initialized");
|
|
1753
|
+
return this.utilityApi.estimatePerformance();
|
|
1754
|
+
}
|
|
1755
|
+
async analyzeFrame(image) {
|
|
1756
|
+
this.checkWasmInitialization("Unable to analyze the frame, cpp API hasn't been initialized");
|
|
1757
|
+
await this.allocateImageBuffers(image.width, image.height);
|
|
1758
|
+
this.inputImageBuffer.set(image.data);
|
|
1759
|
+
this.utilityApi.analyzeFrame();
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Free allocated memory
|
|
1763
|
+
*/
|
|
1764
|
+
async freeResources() {
|
|
1765
|
+
this.versionsFile = null;
|
|
1766
|
+
this.modelsBuffers = null;
|
|
1767
|
+
this.inputImageBuffer = null;
|
|
1768
|
+
this.imageWidth_ = null;
|
|
1769
|
+
this.imageHeight_ = null;
|
|
1770
|
+
this.pipelines_ = null;
|
|
1771
|
+
window.wasmArrayBuffer = null;
|
|
1772
|
+
if (this.pipelineApiUtilities && typeof this.pipelineApiUtilities.delete === "function") {
|
|
1773
|
+
this.pipelineApiUtilities.delete();
|
|
1774
|
+
this.pipelineApiUtilities = null;
|
|
1775
|
+
}
|
|
1776
|
+
if (this.utilityApi && typeof this.utilityApi.delete === "function") {
|
|
1777
|
+
this.utilityApi.delete();
|
|
1778
|
+
this.utilityApi = null;
|
|
1779
|
+
}
|
|
1780
|
+
if (this.idCaptureWasmApi && typeof this.idCaptureWasmApi.delete === "function") {
|
|
1781
|
+
this.idCaptureWasmApi.delete();
|
|
1782
|
+
this.idCaptureWasmApi = null;
|
|
1783
|
+
}
|
|
1784
|
+
if (this.faceDetectionWasmApi && typeof this.faceDetectionWasmApi.delete === "function") {
|
|
1785
|
+
this.faceDetectionWasmApi.delete();
|
|
1786
|
+
this.faceDetectionWasmApi = null;
|
|
1787
|
+
}
|
|
1788
|
+
this.wasmModule = null;
|
|
1789
|
+
this.isInitialized_ = false;
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Clear pipeline state
|
|
1793
|
+
*/
|
|
1794
|
+
resetPipeline(type) {
|
|
1795
|
+
this.checkWasmInitialization("Unable to reset pipeline, cpp API hasn't been initialized");
|
|
1796
|
+
this.pipelineApiUtilities.resetPipeline(this.pipelineTypeToWasmEnum(type));
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Clear all pipelines states
|
|
1800
|
+
*/
|
|
1801
|
+
resetAllPipelines() {
|
|
1802
|
+
this.checkWasmInitialization("Unable to reset pipelines, cpp API hasn't been initialized");
|
|
1803
|
+
for (const [type, model] of this.pipelines_) this.resetPipeline(type);
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Clear all states, not related to ml pipelines
|
|
1807
|
+
*/
|
|
1808
|
+
resetOther() {
|
|
1809
|
+
this.checkWasmInitialization("Unable to reset other states, cpp API hasn't been initialized");
|
|
1810
|
+
this.utilityApi.resetOther();
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Full reset
|
|
1814
|
+
*/
|
|
1815
|
+
reset() {
|
|
1816
|
+
this.resetAllPipelines();
|
|
1817
|
+
this.resetOther();
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Process one frame using one of the pipelines
|
|
1821
|
+
* @param {ImageData} image - input image
|
|
1822
|
+
* @param {WasmPipelineType} type - pipeline type
|
|
1823
|
+
* @returns
|
|
1824
|
+
*/
|
|
1825
|
+
async process(image, type) {
|
|
1826
|
+
this.checkWasmInitialization("Unable to process the image, cpp API hasn't been initialized");
|
|
1827
|
+
if (this.inputImageBuffer === null) throw new Error("Unable to process the image, buffers haven't been allocated!");
|
|
1828
|
+
this.inputImageBuffer.set(image.data);
|
|
1829
|
+
switch (type) {
|
|
1830
|
+
case WasmPipelineType.IdBlurGlarePipeline:
|
|
1831
|
+
case WasmPipelineType.IdBarcodeAndTextQualityPipeline: return this.idCaptureWasmApi.runIdCapturePipeline(this.pipelineTypeToWasmEnum(type));
|
|
1832
|
+
case WasmPipelineType.IdFaceDetectionPipeline: return this.faceDetectionWasmApi.runIdFaceDetectionPipeline();
|
|
1833
|
+
case WasmPipelineType.IdVideoSelfiePipeline: return this.idCaptureWasmApi.runIdVideoSelfiePipeline();
|
|
1834
|
+
case WasmPipelineType.SelfieWithAggregationMetrics:
|
|
1835
|
+
case WasmPipelineType.SelfieWithQualityMetrics: return this.faceDetectionWasmApi.runSelfieFaceDetectionPipeline(this.pipelineTypeToWasmEnum(type));
|
|
1836
|
+
default: throw new Error("Unknown pipeline type");
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
async runIdCapture(image) {
|
|
1840
|
+
this.checkWasmInitialization("Unable to run Id Capture, cpp API hasn't been initialized");
|
|
1841
|
+
if (this.inputImageBuffer === null) throw new Error("Unable to process the image, buffers haven't been allocated!");
|
|
1842
|
+
this.inputImageBuffer.set(image.data);
|
|
1843
|
+
return this.idCaptureWasmApi.runIdCapture();
|
|
1844
|
+
}
|
|
1845
|
+
async runSelfieCapture(image) {
|
|
1846
|
+
this.checkWasmInitialization("Unable to run Selfie Capture, cpp API hasn't been initialized");
|
|
1847
|
+
if (this.inputImageBuffer === null) throw new Error("Unable to process the image, buffers haven't been allocated!");
|
|
1848
|
+
this.inputImageBuffer.set(image.data);
|
|
1849
|
+
return this.faceDetectionWasmApi.runSelfieCapture();
|
|
1850
|
+
}
|
|
1851
|
+
async setFacePositionConstraints(type, minX, minY, maxX, maxY) {
|
|
1852
|
+
this.checkWasmInitialization("Unable to set face position constraints, cpp API hasn't been initialized");
|
|
1853
|
+
this.faceDetectionWasmApi.setFacePositionConstraints(this.pipelineTypeToWasmEnum(type), minX, minY, maxX, maxY);
|
|
1854
|
+
}
|
|
1855
|
+
async setFaceDetectionThresholds(type, brightnessThreshold, blurrinessThreshold, tiltRotationAngleThreshold, minMagicCropSize, autocaptureInterval, minFaceQualityScore, faceOcclusionThreshold) {
|
|
1856
|
+
this.checkWasmInitialization("Unable to set face detection thresholds, cpp API hasn't been initialized");
|
|
1857
|
+
this.faceDetectionWasmApi.setFaceDetectionThresholds(this.pipelineTypeToWasmEnum(type), brightnessThreshold, blurrinessThreshold, tiltRotationAngleThreshold, minMagicCropSize, autocaptureInterval, minFaceQualityScore, faceOcclusionThreshold);
|
|
1858
|
+
}
|
|
1859
|
+
async setFaceAttributesThresholds(type, headwearThreshold, lensesThreshold, closedEyesThreshold, maskThreshold) {
|
|
1860
|
+
this.checkWasmInitialization("Unable to set face attributes thresholds, cpp API hasn't been initialized");
|
|
1861
|
+
this.faceDetectionWasmApi.setFaceAttributesThresholds(this.pipelineTypeToWasmEnum(type), headwearThreshold, lensesThreshold, closedEyesThreshold, maskThreshold);
|
|
1862
|
+
}
|
|
1863
|
+
async setFaceChecksEnabled(type, isLensesCheckEnabled, isMaskCheckEnabled, isClosedEyesCheckEnabled, isHeadWearCheckEnabled, isOcclusionCheckEnabled) {
|
|
1864
|
+
this.checkWasmInitialization("Unable to set face checks enabled flags, cpp API hasn't been initialized");
|
|
1865
|
+
this.faceDetectionWasmApi.setFaceChecksEnabled(this.pipelineTypeToWasmEnum(type), isLensesCheckEnabled, isMaskCheckEnabled, isClosedEyesCheckEnabled, isHeadWearCheckEnabled, isOcclusionCheckEnabled);
|
|
1866
|
+
}
|
|
1867
|
+
async setFaceDetectionMode(type, isVideoSelfie) {
|
|
1868
|
+
this.checkWasmInitialization("Unable to set face detection mode, cpp API hasn't been initialized");
|
|
1869
|
+
this.faceDetectionWasmApi.setFaceDetectionMode(this.pipelineTypeToWasmEnum(type), isVideoSelfie);
|
|
1870
|
+
}
|
|
1871
|
+
async setFaceDetectionCallbacks(type, onFarAway, onTooClose, onTooManyFaces, onNoFace, onCapture, onGetReady, onGetReadyFinished, onCenterFace, onDark, onBlur, onFaceAngle, onBestShot, onLenses, onMask, onEyesClosed, onHeadWear, onSwitchToManualCapture, onFaceOccluded) {
|
|
1872
|
+
this.checkWasmInitialization("Unable to set face detection callbacks, cpp API hasn't been initialized");
|
|
1873
|
+
this.faceDetectionWasmApi.setFaceDetectionCallbacks(this.pipelineTypeToWasmEnum(type), onFarAway, onTooClose, onTooManyFaces, onNoFace, onCapture, onGetReady, onGetReadyFinished, onCenterFace, onDark, onBlur, onFaceAngle, onBestShot, onLenses, onMask, onEyesClosed, onHeadWear, onSwitchToManualCapture, onFaceOccluded);
|
|
1874
|
+
}
|
|
1875
|
+
async setIdCaptureThresholds(type, blurThreshold, blurChangeThreshold, glareThreshold, clsThreshold, sideThreshold, iouThreshold, idDetectedTimeout, autocaptureTimeout, framesAggregationInterval, minFaceIdQualityScore) {
|
|
1876
|
+
this.checkWasmInitialization("Unable to set thresholds, cpp API hasn't been initialized");
|
|
1877
|
+
this.idCaptureWasmApi.setIdCaptureThresholds(this.pipelineTypeToWasmEnum(type), blurThreshold, blurChangeThreshold, glareThreshold, clsThreshold, sideThreshold, iouThreshold, idDetectedTimeout, autocaptureTimeout, framesAggregationInterval, minFaceIdQualityScore);
|
|
1878
|
+
}
|
|
1879
|
+
async setIdCaptureCallbacks(type, onFarAway, onDetectionStarted, onMaskChange, onBlur, onGlare, onCapturing, onCapture, onBestFrame, onIDNotDetected, onSwitchToManualCapture, onIDTypeChange, onIDSideChange, onCapturingCounterValueChange) {
|
|
1880
|
+
this.checkWasmInitialization("Unable to set callbacks, cpp API hasn't been initialized");
|
|
1881
|
+
this.idCaptureWasmApi.setIdCaptureCallbacks(this.pipelineTypeToWasmEnum(type), onFarAway, onDetectionStarted, onMaskChange, onBlur, onGlare, onCapturing, onCapture, onBestFrame, onIDNotDetected, onSwitchToManualCapture, onIDTypeChange, onIDSideChange, onCapturingCounterValueChange);
|
|
1882
|
+
}
|
|
1883
|
+
async setIdCaptureGeometryParams(type, areaDown, areaUp, areaIOSPassportUp, areaIOSPassportDown, widthIOSUp, widthIOSDown, widthDown, widthUp, windowOuterWidth, windowOuterHeight, windowInnerWidth, windowInnerHeight) {
|
|
1884
|
+
this.checkWasmInitialization("Unable to set geometry params, cpp API hasn't been initialized");
|
|
1885
|
+
this.idCaptureWasmApi.setIdCaptureGeometryParams(this.pipelineTypeToWasmEnum(type), areaDown, areaUp, areaIOSPassportUp, areaIOSPassportDown, widthIOSUp, widthIOSDown, widthDown, widthUp, windowOuterWidth, windowOuterHeight, windowInnerWidth, windowInnerHeight);
|
|
1886
|
+
}
|
|
1887
|
+
async setIdCaptureConfigParams(type, isFixedMask, isIPhone14OrHigher$1, idType, isBlurCheckEnabled, isGlareCheckEnabled, isIdFaceQualityCheckEnabled, isIouCheckEnabled) {
|
|
1888
|
+
this.checkWasmInitialization("Unable to set config params, cpp API hasn't been initialized");
|
|
1889
|
+
this.idCaptureWasmApi.setIdCaptureConfigParams(this.pipelineTypeToWasmEnum(type), isFixedMask, isIPhone14OrHigher$1, idType, isBlurCheckEnabled, isGlareCheckEnabled, isIdFaceQualityCheckEnabled, isIouCheckEnabled);
|
|
1890
|
+
}
|
|
1891
|
+
setIdCaptureModelType(pipelineType, modelType) {
|
|
1892
|
+
this.checkWasmInitialization("Unable to set model type, cpp API hasn't been initialized");
|
|
1893
|
+
this.idCaptureWasmApi.setIdCaptureModelType(this.pipelineTypeToWasmEnum(pipelineType), this.IdCaptureModelTypeToWasmEnum(modelType));
|
|
1894
|
+
}
|
|
1895
|
+
IdPerspectiveTransform(image, frameRect) {
|
|
1896
|
+
this.checkWasmInitialization("Unable to perform perspective transform, cpp API hasn't been initialized");
|
|
1897
|
+
return this.idCaptureWasmApi.IdPerspectiveTransform(image, frameRect);
|
|
1898
|
+
}
|
|
1899
|
+
async getVersions() {
|
|
1900
|
+
const text = await (await fetch(this.versionsFile)).text();
|
|
1901
|
+
return JSON.parse(text);
|
|
1902
|
+
}
|
|
1903
|
+
async loadWasm(useSimd, webLibPath, webLibPathSimd) {
|
|
1904
|
+
const libPath = useSimd ? webLibPathSimd : webLibPath;
|
|
1905
|
+
try {
|
|
1906
|
+
window.wasmArrayBuffer = await fetch(libPath).then((response) => response.arrayBuffer()).then((buffer) => new Uint8Array(buffer));
|
|
1907
|
+
this.wasmModule = await this.Module();
|
|
1908
|
+
} catch (e) {
|
|
1909
|
+
console.log(e, useSimd, "useSmd");
|
|
1910
|
+
if (useSimd) return await this.loadWasm(false, webLibPath, webLibPathSimd);
|
|
1911
|
+
else throw e;
|
|
1912
|
+
}
|
|
1913
|
+
return useSimd;
|
|
1914
|
+
}
|
|
1915
|
+
async loadModels() {
|
|
1916
|
+
this.checkWasmInitialization("Unable load the models, cpp API hasn't been initialized");
|
|
1917
|
+
const buffSizes = new this.wasmModule.PipelineTypeToIntMap();
|
|
1918
|
+
const modelData = /* @__PURE__ */ new Map();
|
|
1919
|
+
const loadedModels = /* @__PURE__ */ new Map();
|
|
1920
|
+
for (const [type, modelsPaths] of this.pipelines_) {
|
|
1921
|
+
const wasmType = this.pipelineTypeToWasmEnum(type);
|
|
1922
|
+
const pipelineBuffers = [];
|
|
1923
|
+
const sizes = new this.wasmModule.VectorInt();
|
|
1924
|
+
for (const path of modelsPaths) {
|
|
1925
|
+
if (!loadedModels.has(path)) {
|
|
1926
|
+
const model$1 = await fetch(path).then((response) => response.arrayBuffer()).then((buffer) => new Uint8Array(buffer));
|
|
1927
|
+
loadedModels.set(path, model$1);
|
|
1928
|
+
}
|
|
1929
|
+
const model = loadedModels.get(path);
|
|
1930
|
+
sizes.push_back(model.byteLength);
|
|
1931
|
+
pipelineBuffers.push(model);
|
|
1932
|
+
}
|
|
1933
|
+
buffSizes.set(wasmType, sizes);
|
|
1934
|
+
modelData.set(wasmType, pipelineBuffers);
|
|
1935
|
+
}
|
|
1936
|
+
this.modelsBuffers = this.pipelineApiUtilities.allocateModelsBuffers(buffSizes);
|
|
1937
|
+
for (const [type, data] of modelData) {
|
|
1938
|
+
const buffers = this.modelsBuffers?.get(type);
|
|
1939
|
+
for (let k = 0; k < buffers.size(); k++) {
|
|
1940
|
+
const buffer = buffers?.get(k);
|
|
1941
|
+
if (buffer) buffer.set(data[k]);
|
|
1942
|
+
else throw new Error("Unable to get model buffer from shared memory!");
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
async initializePipelines() {
|
|
1947
|
+
this.checkWasmInitialization("Unable to initialize pipelines, cpp API hasn't been initialized");
|
|
1948
|
+
if (!this.modelsBuffers) await this.loadModels();
|
|
1949
|
+
this.pipelineApiUtilities.initializePipelines();
|
|
1950
|
+
}
|
|
1951
|
+
async setProductionMode(productionMode) {
|
|
1952
|
+
this.checkWasmInitialization("Unable to set production mode, cpp API hasn't been initialized");
|
|
1953
|
+
this.utilityApi.setProductionMode(productionMode);
|
|
1954
|
+
}
|
|
1955
|
+
getPipelineState() {
|
|
1956
|
+
this.checkWasmInitialization("Unable to get pipeline state, cpp API hasn't been initialized");
|
|
1957
|
+
return this.pipelineApiUtilities.getPipelineState();
|
|
1958
|
+
}
|
|
1959
|
+
getCurrentPipeline() {
|
|
1960
|
+
this.checkWasmInitialization("Unable to get current pipeline, cpp API hasn't been initialized");
|
|
1961
|
+
return this.pipelineTypeFromWasmEnum(this.pipelineApiUtilities.getCurrentPipeline());
|
|
1962
|
+
}
|
|
1963
|
+
setSdkVersion(sdkVersion) {
|
|
1964
|
+
this.checkWasmInitialization("Unable to set sdk version, cpp API hasn't been initialized");
|
|
1965
|
+
this.utilityApi.setSdkVersion(sdkVersion);
|
|
1966
|
+
}
|
|
1967
|
+
setSdkPlatform(sdkPlatform) {
|
|
1968
|
+
this.checkWasmInitialization("Unable to set device type, cpp API hasn't been initialized");
|
|
1969
|
+
this.utilityApi.setSdkPlatform(sdkPlatform);
|
|
1970
|
+
}
|
|
1971
|
+
setDeviceInfo(deviceInfo, overrideExisting = true) {
|
|
1972
|
+
this.checkWasmInitialization("Unable to set device info, cpp API hasn't been initialized");
|
|
1973
|
+
this.utilityApi.setDeviceInfo(deviceInfo, overrideExisting);
|
|
1974
|
+
}
|
|
1975
|
+
setBrowserInfo(browserInfo, overrideExisting = true) {
|
|
1976
|
+
this.checkWasmInitialization("Unable to set browser info, cpp API hasn't been initialized");
|
|
1977
|
+
this.utilityApi.setBrowserInfo(browserInfo, overrideExisting);
|
|
1978
|
+
}
|
|
1979
|
+
setCameraInfo(cameraInfo, overrideExisting = true) {
|
|
1980
|
+
this.checkWasmInitialization("Unable to set camera info, cpp API hasn't been initialized");
|
|
1981
|
+
this.utilityApi.setCameraInfo(cameraInfo, overrideExisting);
|
|
1982
|
+
}
|
|
1983
|
+
setZc(zc) {
|
|
1984
|
+
this.checkWasmInitialization("Unable to set zc, cpp API hasn't been initialized");
|
|
1985
|
+
this.utilityApi.setZc(zc);
|
|
1986
|
+
}
|
|
1987
|
+
setMotionStatus(motionStatus) {
|
|
1988
|
+
this.checkWasmInitialization("Unable to set motion status, cpp API hasn't been initialized");
|
|
1989
|
+
this.utilityApi.setMotionStatus(motionStatus);
|
|
1990
|
+
}
|
|
1991
|
+
setMetadataField(key, value) {
|
|
1992
|
+
this.checkWasmInitialization("Unable to set metadata field, cpp API hasn't been initialized");
|
|
1993
|
+
this.utilityApi.setMetadataField(key, value);
|
|
1994
|
+
}
|
|
1995
|
+
setInspectorOpened(inspectorOpened) {
|
|
1996
|
+
this.inspectorOpened_ = inspectorOpened;
|
|
1997
|
+
}
|
|
1998
|
+
setBackgroundMode(backgroundMode) {
|
|
1999
|
+
this.checkWasmInitialization("Unable to set background mode, cpp API hasn't been initialized");
|
|
2000
|
+
this.utilityApi.setBackgroundMode(backgroundMode);
|
|
2001
|
+
}
|
|
2002
|
+
getCheck() {
|
|
2003
|
+
this.checkWasmInitialization("Unable to get check, cpp API hasn't been initialized");
|
|
2004
|
+
return this.utilityApi.getCheck();
|
|
2005
|
+
}
|
|
2006
|
+
getMetadata() {
|
|
2007
|
+
this.checkWasmInitialization("Unable to get metadata, cpp API hasn't been initialized");
|
|
2008
|
+
this.utilityApi.setInspectorOpened(this.inspectorOpened_);
|
|
2009
|
+
return this.utilityApi.getMetadata();
|
|
2010
|
+
}
|
|
2011
|
+
async prc() {
|
|
2012
|
+
this.checkWasmInitialization("Unable to prc, cpp API hasn't been initialized");
|
|
2013
|
+
return await this.wasmCallWrapper(this.utilityApi.prc.bind(this.utilityApi), []);
|
|
2014
|
+
}
|
|
2015
|
+
async poc(output) {
|
|
2016
|
+
this.checkWasmInitialization("Unable to poc, cpp API hasn't been initialized");
|
|
2017
|
+
await this.wasmCallWrapper(this.utilityApi.poc.bind(this.utilityApi), [output]);
|
|
2018
|
+
}
|
|
2019
|
+
async pc(deviceId) {
|
|
2020
|
+
this.checkWasmInitialization("Unable to pc, cpp API hasn't been initialized");
|
|
2021
|
+
await this.wasmCallWrapper(this.utilityApi.pc.bind(this.utilityApi), [deviceId]);
|
|
2022
|
+
}
|
|
2023
|
+
ckvcks(data) {
|
|
2024
|
+
this.checkWasmInitialization("Unable to ckvcks, cpp API hasn't been initialized");
|
|
2025
|
+
this.utilityApi.ckvcks(data);
|
|
2026
|
+
}
|
|
2027
|
+
pipelineTypeToWasmEnum(type) {
|
|
2028
|
+
switch (type) {
|
|
2029
|
+
case WasmPipelineType.IdBlurGlarePipeline: return this.wasmModule.PipelineType.IdBlurGlarePipeline;
|
|
2030
|
+
case WasmPipelineType.IdBarcodeAndTextQualityPipeline: return this.wasmModule.PipelineType.IdBarcodeAndTextQualityPipeline;
|
|
2031
|
+
case WasmPipelineType.IdFaceDetectionPipeline: return this.wasmModule.PipelineType.IdFaceDetectionPipeline;
|
|
2032
|
+
case WasmPipelineType.IdVideoSelfiePipeline: return this.wasmModule.PipelineType.IdVideoSelfiePipeline;
|
|
2033
|
+
case WasmPipelineType.SelfieWithAggregationMetrics: return this.wasmModule.PipelineType.SelfieWithAggregationMetrics;
|
|
2034
|
+
case WasmPipelineType.SelfieWithQualityMetrics: return this.wasmModule.PipelineType.SelfieWithQualityMetrics;
|
|
2035
|
+
default: throw new Error("Unknown pipeline type");
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
pipelineTypeFromWasmEnum(type) {
|
|
2039
|
+
switch (type) {
|
|
2040
|
+
case this.wasmModule.PipelineType.IdBlurGlarePipeline: return WasmPipelineType.IdBlurGlarePipeline;
|
|
2041
|
+
case this.wasmModule.IdBarcodeAndTextQualityPipeline: return WasmPipelineType.IdBarcodeAndTextQualityPipeline;
|
|
2042
|
+
case this.wasmModule.PipelineType.IdFaceDetectionPipeline: return WasmPipelineType.IdFaceDetectionPipeline;
|
|
2043
|
+
case this.wasmModule.PipelineType.IdVideoSelfiePipeline: return WasmPipelineType.IdVideoSelfiePipeline;
|
|
2044
|
+
case this.wasmModule.PipelineType.SelfieWithAggregationMetrics: return WasmPipelineType.SelfieWithAggregationMetrics;
|
|
2045
|
+
case this.wasmModule.PipelineType.SelfieWithQualityMetrics: return WasmPipelineType.SelfieWithQualityMetrics;
|
|
2046
|
+
default: throw new Error("Unknown pipeline type");
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
IdCaptureModelTypeToWasmEnum(type) {
|
|
2050
|
+
switch (type) {
|
|
2051
|
+
case IdCaptureModelType.IdCaptureV1x: return this.wasmModule.IdCaptureModelType.IdCaptureV1x;
|
|
2052
|
+
case IdCaptureModelType.IdCaptureV2x: return this.wasmModule.IdCaptureModelType.IdCaptureV2x;
|
|
2053
|
+
case IdCaptureModelType.IdCaptureV3x: return this.wasmModule.IdCaptureModelType.IdCaptureV3x;
|
|
2054
|
+
default: throw new Error("Unknown Id Capture model type");
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
IdCaptureModelTypeFromWasmEnum(type) {
|
|
2058
|
+
switch (type) {
|
|
2059
|
+
case this.wasmModule.IdCaptureModelType.IdCaptureV1x: return IdCaptureModelType.IdCaptureV1x;
|
|
2060
|
+
case this.wasmModule.IdCaptureModelType.IdCaptureV2x: return IdCaptureModelType.IdCaptureV2x;
|
|
2061
|
+
case this.wasmModule.IdCaptureModelType.IdCaptureV3x: return IdCaptureModelType.IdCaptureV3x;
|
|
2062
|
+
default: throw new Error("Unknown Id Capture model type");
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
checkWasmInitialization(message) {
|
|
2066
|
+
if (!this.isInitialized()) throw new Error(message);
|
|
2067
|
+
}
|
|
2068
|
+
async wasmCallWrapper(wasmMethod, args = []) {
|
|
2069
|
+
return await this.wasmCallMutex.withLock(async (...args$1) => {
|
|
2070
|
+
let isFinished = false;
|
|
2071
|
+
try {
|
|
2072
|
+
while (true) {
|
|
2073
|
+
isFinished = wasmMethod(...args$1);
|
|
2074
|
+
if (isFinished) return;
|
|
2075
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2076
|
+
}
|
|
2077
|
+
} catch (e) {
|
|
2078
|
+
console.log("Error in wasmCallWrapper:", e);
|
|
2079
|
+
}
|
|
2080
|
+
}, ...args);
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
var mlWasmJSApi_default = MlWasmJSApi.getInstance();
|
|
2084
|
+
|
|
2085
|
+
//#endregion
|
|
2086
|
+
//#region ../infra/src/wasm/warmup.ts
|
|
2087
|
+
/** Array of all available pipelines */
|
|
2088
|
+
const WASM_PIPELINES = ["selfie", "idCapture"];
|
|
2089
|
+
/**
|
|
2090
|
+
* Default model files for each pipeline.
|
|
2091
|
+
* These are the ONNX model files required by each ML pipeline.
|
|
2092
|
+
*/
|
|
2093
|
+
const DEFAULT_PIPELINE_MODELS = {
|
|
2094
|
+
selfie: [
|
|
2095
|
+
"selfie_bf_angles_192x192_opset9_fp16.ortmodelv2",
|
|
2096
|
+
"face_attributes_v1_3_fp16.ortmodelv2",
|
|
2097
|
+
"mls_regressor_4773007c657b4f05a460321456740d0f_fp16.ortmodelv2",
|
|
2098
|
+
"face_occlusion_v0_1_fp16.ortmodelv2"
|
|
2099
|
+
],
|
|
2100
|
+
idCapture: ["id_capture_2_01_fp16.ortmodelv2", "id_fiqa_19a81a0b9bf6492eb03b4667f6db4c85_fp16.ortmodelv2"]
|
|
2101
|
+
};
|
|
2102
|
+
let state = "idle";
|
|
2103
|
+
let loadingPromise = null;
|
|
2104
|
+
let wasmError = null;
|
|
2105
|
+
let loadedPipelines = [];
|
|
2106
|
+
let lastConfig = null;
|
|
2107
|
+
function mapPipelineToWasmType(pipeline) {
|
|
2108
|
+
switch (pipeline) {
|
|
2109
|
+
case "selfie": return WasmPipelineType.SelfieWithQualityMetrics;
|
|
2110
|
+
case "idCapture": return WasmPipelineType.IdBlurGlarePipeline;
|
|
2111
|
+
default: throw new Error(`Unknown pipeline: ${pipeline}`);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Derives the models base path from the WASM path.
|
|
2116
|
+
* Assumes models are in a 'models' subdirectory relative to the WASM binary location.
|
|
2117
|
+
* e.g., '/wasm/v2/binary.wasm' -> '/wasm/v2/models'
|
|
2118
|
+
*/
|
|
2119
|
+
function deriveModelsBasePath(wasmPath) {
|
|
2120
|
+
const lastSlashIndex = wasmPath.lastIndexOf("/");
|
|
2121
|
+
if (lastSlashIndex === -1) return "models";
|
|
2122
|
+
return `${wasmPath.substring(0, lastSlashIndex)}/models`;
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
2125
|
+
* Builds the pipelines map with model paths for WASM initialization.
|
|
2126
|
+
* @param pipelines - Array of pipeline types to initialize
|
|
2127
|
+
* @param modelsBasePath - Base path where model files are located
|
|
2128
|
+
* @param customModels - Optional custom model files per pipeline
|
|
2129
|
+
*/
|
|
2130
|
+
function buildPipelinesMap(pipelines, modelsBasePath, customModels) {
|
|
2131
|
+
const pipelinesMap = /* @__PURE__ */ new Map();
|
|
2132
|
+
for (const pipeline of pipelines) {
|
|
2133
|
+
const wasmType = mapPipelineToWasmType(pipeline);
|
|
2134
|
+
if (!pipelinesMap.has(wasmType)) {
|
|
2135
|
+
const modelPaths = (customModels?.[pipeline] ?? DEFAULT_PIPELINE_MODELS[pipeline]).map((modelFile) => `${modelsBasePath}/${modelFile}`);
|
|
2136
|
+
pipelinesMap.set(wasmType, modelPaths);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
return pipelinesMap;
|
|
2140
|
+
}
|
|
2141
|
+
/**
|
|
2142
|
+
* Preloads WASM binary and ML models at app startup.
|
|
2143
|
+
* Runs asynchronously - app can continue while WASM loads in background.
|
|
2144
|
+
*
|
|
2145
|
+
* Model files are automatically loaded based on the pipeline type.
|
|
2146
|
+
* By default, models are expected in a 'models' subdirectory relative to the WASM binary.
|
|
2147
|
+
*
|
|
2148
|
+
* @param config - Configuration for WASM warmup
|
|
2149
|
+
* @returns Promise that resolves when WASM is ready
|
|
2150
|
+
*
|
|
2151
|
+
* @example
|
|
2152
|
+
* ```ts
|
|
2153
|
+
* // Basic usage - models loaded from /wasm/v2/models/
|
|
2154
|
+
* warmupWasm({
|
|
2155
|
+
* wasmPath: '/wasm/v2/ml-wasm.wasm',
|
|
2156
|
+
* glueCodePath: '/wasm/v2/ml-wasm.js',
|
|
2157
|
+
* pipelines: ['selfie', 'idCapture'],
|
|
2158
|
+
* });
|
|
2159
|
+
*
|
|
2160
|
+
* // With explicit models path
|
|
2161
|
+
* warmupWasm({
|
|
2162
|
+
* wasmPath: '/wasm/v2/ml-wasm.wasm',
|
|
2163
|
+
* glueCodePath: '/wasm/v2/ml-wasm.js',
|
|
2164
|
+
* modelsBasePath: 'https://cdn.example.com/wasm/v2/models',
|
|
2165
|
+
* pipelines: ['selfie'],
|
|
2166
|
+
* });
|
|
2167
|
+
* ```
|
|
2168
|
+
*/
|
|
2169
|
+
async function warmupWasm(config) {
|
|
2170
|
+
const pipelines = config.pipelines ?? [...WASM_PIPELINES];
|
|
2171
|
+
const modelsBasePath = config.modelsBasePath ?? deriveModelsBasePath(config.wasmPath);
|
|
2172
|
+
if (state === "loading" && loadingPromise) return loadingPromise;
|
|
2173
|
+
if (state === "ready") {
|
|
2174
|
+
if (pipelines.filter((p) => !loadedPipelines.includes(p)).length === 0) return;
|
|
2175
|
+
if (!lastConfig) throw new Error("Cannot add pipelines: original warmup config not available");
|
|
2176
|
+
if (lastConfig.wasmPath !== config.wasmPath || lastConfig.glueCodePath !== config.glueCodePath || (lastConfig.wasmSimdPath ?? lastConfig.wasmPath) !== (config.wasmSimdPath ?? config.wasmPath) || (lastConfig.useSimd ?? true) !== (config.useSimd ?? true)) throw new Error("Cannot add pipelines: WASM config mismatch. Use same wasmPath, glueCodePath, and useSimd settings.");
|
|
2177
|
+
const combinedPipelines = [...new Set([...loadedPipelines, ...pipelines])];
|
|
2178
|
+
state = "loading";
|
|
2179
|
+
wasmError = null;
|
|
2180
|
+
loadingPromise = (async () => {
|
|
2181
|
+
try {
|
|
2182
|
+
const pipelinesMap = buildPipelinesMap(combinedPipelines, lastConfig.modelsBasePath ?? deriveModelsBasePath(lastConfig.wasmPath), lastConfig.pipelineModels);
|
|
2183
|
+
await mlWasmJSApi_default.initialize(lastConfig.wasmPath, lastConfig.wasmSimdPath ?? lastConfig.wasmPath, lastConfig.glueCodePath, lastConfig.useSimd ?? true, "", pipelinesMap);
|
|
2184
|
+
await mlWasmJSApi_default.loadModels();
|
|
2185
|
+
await mlWasmJSApi_default.initializePipelines();
|
|
2186
|
+
state = "ready";
|
|
2187
|
+
loadedPipelines = combinedPipelines;
|
|
2188
|
+
lastConfig = {
|
|
2189
|
+
...lastConfig,
|
|
2190
|
+
pipelines: combinedPipelines
|
|
2191
|
+
};
|
|
2192
|
+
loadingPromise = null;
|
|
2193
|
+
} catch (error) {
|
|
2194
|
+
state = "error";
|
|
2195
|
+
wasmError = error instanceof Error ? error : new Error(String(error));
|
|
2196
|
+
loadingPromise = null;
|
|
2197
|
+
throw wasmError;
|
|
2198
|
+
}
|
|
2199
|
+
})();
|
|
2200
|
+
return loadingPromise;
|
|
2201
|
+
}
|
|
2202
|
+
state = "loading";
|
|
2203
|
+
wasmError = null;
|
|
2204
|
+
loadingPromise = (async () => {
|
|
2205
|
+
try {
|
|
2206
|
+
const pipelinesMap = buildPipelinesMap(pipelines, modelsBasePath, config.pipelineModels);
|
|
2207
|
+
await mlWasmJSApi_default.initialize(config.wasmPath, config.wasmSimdPath ?? config.wasmPath, config.glueCodePath, config.useSimd ?? true, "", pipelinesMap);
|
|
2208
|
+
await mlWasmJSApi_default.loadModels();
|
|
2209
|
+
await mlWasmJSApi_default.initializePipelines();
|
|
2210
|
+
state = "ready";
|
|
2211
|
+
loadedPipelines = [...pipelines];
|
|
2212
|
+
lastConfig = {
|
|
2213
|
+
...config,
|
|
2214
|
+
pipelines
|
|
2215
|
+
};
|
|
2216
|
+
loadingPromise = null;
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
state = "error";
|
|
2219
|
+
wasmError = error instanceof Error ? error : new Error(String(error));
|
|
2220
|
+
loadingPromise = null;
|
|
2221
|
+
throw wasmError;
|
|
2222
|
+
}
|
|
2223
|
+
})();
|
|
2224
|
+
return loadingPromise;
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Returns the current warmup status.
|
|
2228
|
+
* Useful for showing loading indicators.
|
|
2229
|
+
*
|
|
2230
|
+
* @returns Current warmup status
|
|
2231
|
+
*/
|
|
2232
|
+
function getWasmStatus() {
|
|
2233
|
+
return {
|
|
2234
|
+
isReady: state === "ready",
|
|
2235
|
+
isLoading: state === "loading",
|
|
2236
|
+
error: wasmError ?? void 0,
|
|
2237
|
+
loadedPipelines: [...loadedPipelines]
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Returns true if WASM is fully initialized and ready.
|
|
2242
|
+
*
|
|
2243
|
+
* @returns True if WASM is ready
|
|
2244
|
+
*/
|
|
2245
|
+
function isWasmReady() {
|
|
2246
|
+
return state === "ready";
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Returns a promise that resolves when WASM is ready.
|
|
2250
|
+
* - If ready: resolves immediately
|
|
2251
|
+
* - If loading: waits for completion, then resolves
|
|
2252
|
+
* - If not started: rejects with error
|
|
2253
|
+
*
|
|
2254
|
+
* @returns Promise that resolves when WASM is ready
|
|
2255
|
+
* @throws Error if WASM is in error state or was never started
|
|
2256
|
+
*/
|
|
2257
|
+
function waitForWasm() {
|
|
2258
|
+
if (state === "ready") return Promise.resolve();
|
|
2259
|
+
if (state === "loading" && loadingPromise) return loadingPromise;
|
|
2260
|
+
if (state === "error") return Promise.reject(wasmError ?? /* @__PURE__ */ new Error("WASM initialization failed"));
|
|
2261
|
+
return Promise.reject(/* @__PURE__ */ new Error("WASM warmup was not started. Call warmupWasm() first."));
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
//#endregion
|
|
2265
|
+
//#region ../infra/src/providers/wasm/BaseWasmProvider.ts
|
|
2266
|
+
/**
|
|
2267
|
+
* Base provider class that abstracts common WASM functionality.
|
|
2268
|
+
* This serves as a foundation for specific ML capability providers
|
|
2269
|
+
* like FaceDetectionProvider and IdCaptureProvider.
|
|
2270
|
+
*/
|
|
2271
|
+
var BaseWasmProvider = class {
|
|
2272
|
+
/**
|
|
2273
|
+
* Creates a new BaseWasmProvider
|
|
2274
|
+
* @param pipelineType - The WASM pipeline type this provider uses
|
|
2275
|
+
*/
|
|
2276
|
+
constructor(pipelineType) {
|
|
2277
|
+
this._isInitialized = false;
|
|
2278
|
+
this.pipelineType = pipelineType;
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Returns whether this provider has been initialized.
|
|
2282
|
+
*/
|
|
2283
|
+
get initialized() {
|
|
2284
|
+
return this._isInitialized;
|
|
2285
|
+
}
|
|
2286
|
+
getPipelineType() {
|
|
2287
|
+
if (this.pipelineType === void 0) throw new Error(`${this.constructor.name} has no pipeline type configured.`);
|
|
2288
|
+
return this.pipelineType;
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Initializes the provider by ensuring WASM is loaded
|
|
2292
|
+
* @param config - Provider configuration
|
|
2293
|
+
* @param pipeline - The pipeline type to warm up ('selfie', 'idCapture', etc.)
|
|
2294
|
+
*/
|
|
2295
|
+
async initializeBase(config, pipeline) {
|
|
2296
|
+
if (this._isInitialized) return;
|
|
2297
|
+
if (getWasmStatus().isLoading || isWasmReady()) await waitForWasm();
|
|
2298
|
+
else {
|
|
2299
|
+
if (!config.wasmPath || !config.glueCodePath) throw new Error("WASM path and glue code path are required. Call warmupWasm() first.");
|
|
2300
|
+
await warmupWasm({
|
|
2301
|
+
wasmPath: config.wasmPath,
|
|
2302
|
+
wasmSimdPath: config.wasmSimdPath,
|
|
2303
|
+
glueCodePath: config.glueCodePath,
|
|
2304
|
+
useSimd: config.useSimd,
|
|
2305
|
+
modelsBasePath: config.modelsBasePath,
|
|
2306
|
+
pipelines: [pipeline]
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
this._isInitialized = true;
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* Ensures the provider is initialized before performing operations.
|
|
2313
|
+
* @throws Error if not initialized
|
|
2314
|
+
*/
|
|
2315
|
+
ensureInitialized() {
|
|
2316
|
+
if (!this._isInitialized) throw new Error(`${this.constructor.name} not initialized. Call initialize() first.`);
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Processes a frame through the WASM pipeline
|
|
2320
|
+
* @param image - Image data to process
|
|
2321
|
+
* @returns The pipeline result (type depends on pipeline - WASM returns any)
|
|
2322
|
+
*/
|
|
2323
|
+
async processFrameWasm(image) {
|
|
2324
|
+
this.ensureInitialized();
|
|
2325
|
+
const pipelineType = this.getPipelineType();
|
|
2326
|
+
await mlWasmJSApi_default.allocateImageBuffers(image.width, image.height);
|
|
2327
|
+
await mlWasmJSApi_default.handleDetectionCallAndUpdateState(pipelineType);
|
|
2328
|
+
return await mlWasmJSApi_default.process(image, pipelineType);
|
|
2329
|
+
}
|
|
2330
|
+
/**
|
|
2331
|
+
* Resets the pipeline to its initial state.
|
|
2332
|
+
* Safe to call even if not initialized (no-op in that case).
|
|
2333
|
+
*/
|
|
2334
|
+
reset() {
|
|
2335
|
+
if (this._isInitialized && this.pipelineType !== void 0) mlWasmJSApi_default.resetPipeline(this.pipelineType);
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Disposes of resources and resets initialization state.
|
|
2339
|
+
* Safe to call even if not initialized.
|
|
2340
|
+
*/
|
|
2341
|
+
async dispose() {
|
|
2342
|
+
this.reset();
|
|
2343
|
+
this._isInitialized = false;
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region ../infra/src/providers/wasm/FaceDetectionProvider.ts
|
|
2349
|
+
var FaceDetectionProvider = class extends BaseWasmProvider {
|
|
2350
|
+
constructor() {
|
|
2351
|
+
super(WasmPipelineType.SelfieWithQualityMetrics);
|
|
2352
|
+
this.defaultThresholds = {
|
|
2353
|
+
frameMinX: 0,
|
|
2354
|
+
frameMinY: 0,
|
|
2355
|
+
frameMaxX: 1,
|
|
2356
|
+
frameMaxY: 1,
|
|
2357
|
+
brightnessThreshold: 50,
|
|
2358
|
+
blurrinessThreshold: 50,
|
|
2359
|
+
tiltRotationAngleThreshold: 15,
|
|
2360
|
+
minMagicCropSize: 200,
|
|
2361
|
+
headwearThreshold: .86,
|
|
2362
|
+
lensesThreshold: .95,
|
|
2363
|
+
closedEyesThreshold: .9,
|
|
2364
|
+
maskThreshold: .85,
|
|
2365
|
+
minFaceQualityScore: .63,
|
|
2366
|
+
faceOcclusionThreshold: .3
|
|
2367
|
+
};
|
|
2368
|
+
this.currentFrame = null;
|
|
2369
|
+
this.bestCanvas = null;
|
|
2370
|
+
this.bestFace = null;
|
|
2371
|
+
}
|
|
2372
|
+
async processFrame(image) {
|
|
2373
|
+
this.currentFrame = image;
|
|
2374
|
+
await this.processFrameWasm(image);
|
|
2375
|
+
}
|
|
2376
|
+
async initialize(config) {
|
|
2377
|
+
await this.initializeBase(config, "selfie");
|
|
2378
|
+
this.applyDefaults(config.autocaptureInterval ?? 0);
|
|
2379
|
+
}
|
|
2380
|
+
setCallbacks(callbacks) {
|
|
2381
|
+
this.ensureInitialized();
|
|
2382
|
+
const onBestShotWrapper = (face) => {
|
|
2383
|
+
this.bestFace = face;
|
|
2384
|
+
if (this.currentFrame) {
|
|
2385
|
+
const frameCanvas = IncodeCanvas.fromImageData(this.currentFrame);
|
|
2386
|
+
this.bestCanvas = frameCanvas.clone() ?? frameCanvas;
|
|
2387
|
+
}
|
|
2388
|
+
callbacks.onBestShot?.(face);
|
|
2389
|
+
};
|
|
2390
|
+
const onCaptureWrapper = () => {
|
|
2391
|
+
let frameCanvas = this.bestCanvas;
|
|
2392
|
+
if (!frameCanvas && this.currentFrame) frameCanvas = IncodeCanvas.fromImageData(this.currentFrame);
|
|
2393
|
+
if (!frameCanvas) return;
|
|
2394
|
+
const faceCoordinates = this.bestFace ? this.formatFaceCoordinates(this.bestFace) : this.createDefaultFaceCoordinates(frameCanvas);
|
|
2395
|
+
try {
|
|
2396
|
+
frameCanvas.updateBase64Image();
|
|
2397
|
+
frameCanvas.updateBlob();
|
|
2398
|
+
} catch {}
|
|
2399
|
+
callbacks.onCapture?.(frameCanvas, faceCoordinates);
|
|
2400
|
+
this.bestCanvas = null;
|
|
2401
|
+
this.bestFace = null;
|
|
2402
|
+
};
|
|
2403
|
+
mlWasmJSApi_default.setFaceDetectionCallbacks(this.getPipelineType(), callbacks.onFarAway ?? (() => {}), callbacks.onTooClose ?? (() => {}), callbacks.onTooManyFaces ?? (() => {}), callbacks.onNoFace ?? (() => {}), onCaptureWrapper, callbacks.onGetReady ?? (() => {}), callbacks.onGetReadyFinished ?? (() => {}), callbacks.onCenterFace ?? (() => {}), callbacks.onDark ?? (() => {}), callbacks.onBlur ?? (() => {}), callbacks.onFaceAngle ?? (() => {}), onBestShotWrapper, callbacks.onLenses ?? (() => {}), callbacks.onMask ?? (() => {}), callbacks.onEyesClosed ?? (() => {}), callbacks.onHeadWear ?? (() => {}), callbacks.onSwitchToManualCapture ?? (() => {}), callbacks.onFaceOccluded ?? (() => {}));
|
|
2404
|
+
}
|
|
2405
|
+
setPositionConstraints(constraints) {
|
|
2406
|
+
this.ensureInitialized();
|
|
2407
|
+
mlWasmJSApi_default.setFacePositionConstraints(this.getPipelineType(), constraints.minX, constraints.minY, constraints.maxX, constraints.maxY);
|
|
2408
|
+
}
|
|
2409
|
+
applyDefaults(autocaptureInterval = 0) {
|
|
2410
|
+
this.ensureInitialized();
|
|
2411
|
+
this.setThresholds({
|
|
2412
|
+
brightnessThreshold: this.defaultThresholds.brightnessThreshold,
|
|
2413
|
+
blurrinessThreshold: this.defaultThresholds.blurrinessThreshold,
|
|
2414
|
+
tiltRotationAngleThreshold: this.defaultThresholds.tiltRotationAngleThreshold,
|
|
2415
|
+
minMagicCropSize: this.defaultThresholds.minMagicCropSize,
|
|
2416
|
+
autocaptureInterval,
|
|
2417
|
+
minFaceQualityScore: this.defaultThresholds.minFaceQualityScore,
|
|
2418
|
+
faceOcclusionThreshold: this.defaultThresholds.faceOcclusionThreshold
|
|
2419
|
+
});
|
|
2420
|
+
this.setPositionConstraints({
|
|
2421
|
+
minX: this.defaultThresholds.frameMinX,
|
|
2422
|
+
minY: this.defaultThresholds.frameMinY,
|
|
2423
|
+
maxX: this.defaultThresholds.frameMaxX,
|
|
2424
|
+
maxY: this.defaultThresholds.frameMaxY
|
|
2425
|
+
});
|
|
2426
|
+
this.setAttributesThresholds({
|
|
2427
|
+
headwearThreshold: this.defaultThresholds.headwearThreshold,
|
|
2428
|
+
lensesThreshold: this.defaultThresholds.lensesThreshold,
|
|
2429
|
+
closedEyesThreshold: this.defaultThresholds.closedEyesThreshold,
|
|
2430
|
+
maskThreshold: this.defaultThresholds.maskThreshold
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
setAutocaptureInterval(interval) {
|
|
2434
|
+
this.ensureInitialized();
|
|
2435
|
+
if (!this.currentThresholds) {
|
|
2436
|
+
this.applyDefaults(interval);
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
this.setThresholds({
|
|
2440
|
+
...this.currentThresholds,
|
|
2441
|
+
autocaptureInterval: interval
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
setThresholds(thresholds) {
|
|
2445
|
+
this.ensureInitialized();
|
|
2446
|
+
this.currentThresholds = { ...thresholds };
|
|
2447
|
+
mlWasmJSApi_default.setFaceDetectionThresholds(this.getPipelineType(), thresholds.brightnessThreshold, thresholds.blurrinessThreshold, thresholds.tiltRotationAngleThreshold, thresholds.minMagicCropSize, thresholds.autocaptureInterval, thresholds.minFaceQualityScore, thresholds.faceOcclusionThreshold);
|
|
2448
|
+
}
|
|
2449
|
+
setAttributesThresholds(thresholds) {
|
|
2450
|
+
this.ensureInitialized();
|
|
2451
|
+
mlWasmJSApi_default.setFaceAttributesThresholds(this.getPipelineType(), thresholds.headwearThreshold, thresholds.lensesThreshold, thresholds.closedEyesThreshold, thresholds.maskThreshold);
|
|
2452
|
+
}
|
|
2453
|
+
setChecksEnabled(config) {
|
|
2454
|
+
this.ensureInitialized();
|
|
2455
|
+
mlWasmJSApi_default.setFaceChecksEnabled(this.getPipelineType(), config.lenses, config.mask, config.closedEyes, config.headWear, config.occlusion);
|
|
2456
|
+
}
|
|
2457
|
+
setVideoSelfieMode(enabled) {
|
|
2458
|
+
this.ensureInitialized();
|
|
2459
|
+
mlWasmJSApi_default.setFaceDetectionMode(this.getPipelineType(), enabled);
|
|
2460
|
+
}
|
|
2461
|
+
reset() {
|
|
2462
|
+
super.reset();
|
|
2463
|
+
this.currentFrame = null;
|
|
2464
|
+
this.bestCanvas = null;
|
|
2465
|
+
this.bestFace = null;
|
|
2466
|
+
}
|
|
2467
|
+
createDefaultFaceCoordinates(canvas) {
|
|
2468
|
+
return {
|
|
2469
|
+
rightEyeX: 0,
|
|
2470
|
+
rightEyeY: 0,
|
|
2471
|
+
leftEyeX: 0,
|
|
2472
|
+
leftEyeY: 0,
|
|
2473
|
+
noseTipX: 0,
|
|
2474
|
+
noseTipY: 0,
|
|
2475
|
+
rightMouthX: 0,
|
|
2476
|
+
rightMouthY: 0,
|
|
2477
|
+
mouthX: 0,
|
|
2478
|
+
mouthY: 0,
|
|
2479
|
+
x: 0,
|
|
2480
|
+
y: 0,
|
|
2481
|
+
width: canvas.width() ?? 0,
|
|
2482
|
+
height: canvas.height() ?? 0
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
formatFaceCoordinates(face) {
|
|
2486
|
+
return {
|
|
2487
|
+
rightEyeX: face.rightEye.x,
|
|
2488
|
+
rightEyeY: face.rightEye.y,
|
|
2489
|
+
leftEyeX: face.leftEye.x,
|
|
2490
|
+
leftEyeY: face.leftEye.y,
|
|
2491
|
+
noseTipX: face.noseTip.x,
|
|
2492
|
+
noseTipY: face.noseTip.y,
|
|
2493
|
+
rightMouthX: face.rightMouthCorner.x,
|
|
2494
|
+
rightMouthY: face.rightMouthCorner.y,
|
|
2495
|
+
mouthX: face.leftMouthCorner.x,
|
|
2496
|
+
mouthY: face.leftMouthCorner.y,
|
|
2497
|
+
x: face.rect.x,
|
|
2498
|
+
y: face.rect.y,
|
|
2499
|
+
width: face.rect.width,
|
|
2500
|
+
height: face.rect.height
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
};
|
|
2504
|
+
|
|
2505
|
+
//#endregion
|
|
2506
|
+
//#region ../infra/src/providers/wasm/IdCaptureProvider.ts
|
|
2507
|
+
var IdCaptureProvider = class extends BaseWasmProvider {
|
|
2508
|
+
constructor() {
|
|
2509
|
+
super(WasmPipelineType.IdBlurGlarePipeline);
|
|
2510
|
+
this.lastProcessResult = null;
|
|
2511
|
+
this.capturedCanvas = null;
|
|
2512
|
+
this.originalCapturedCanvas = null;
|
|
2513
|
+
}
|
|
2514
|
+
async initialize(config) {
|
|
2515
|
+
await this.initializeBase(config, "idCapture");
|
|
2516
|
+
}
|
|
2517
|
+
setCallbacks(callbacks) {
|
|
2518
|
+
this.ensureInitialized();
|
|
2519
|
+
const onCaptureWrapper = () => {
|
|
2520
|
+
callbacks.onCapture?.();
|
|
2521
|
+
};
|
|
2522
|
+
mlWasmJSApi_default.setIdCaptureCallbacks(this.getPipelineType(), callbacks.onFarAway ?? (() => {}), callbacks.onDetectionStarted ?? (() => {}), callbacks.onMaskChange ? (show, mask, top, orientation) => callbacks.onMaskChange?.(show, mask, top, orientation) : () => {}, callbacks.onBlur ?? (() => {}), callbacks.onGlare ?? (() => {}), callbacks.onCapturing ?? (() => {}), callbacks.onCapture ? onCaptureWrapper : () => {}, callbacks.onBestFrame ? (blur, glare, orientation) => callbacks.onBestFrame?.(blur, glare, orientation) : () => {}, callbacks.onIdNotDetected ?? (() => {}), callbacks.onSwitchToManualCapture ?? (() => {}), callbacks.onIdTypeChange ? (idType) => callbacks.onIdTypeChange?.(idType) : () => {}, callbacks.onIdSideChange ? (side) => callbacks.onIdSideChange?.(side) : () => {}, callbacks.onCapturingCounterValueChange ? (value) => callbacks.onCapturingCounterValueChange?.(value) : () => {});
|
|
2523
|
+
}
|
|
2524
|
+
setThresholds(thresholds) {
|
|
2525
|
+
this.ensureInitialized();
|
|
2526
|
+
mlWasmJSApi_default.setIdCaptureThresholds(this.getPipelineType(), thresholds.blurThreshold, thresholds.blurChangeThreshold, thresholds.glareThreshold, thresholds.clsThreshold, thresholds.sideThreshold, thresholds.iouThreshold, thresholds.idDetectedTimeout, thresholds.autocaptureTimeout, thresholds.framesAggregationInterval, thresholds.minFaceIdQualityScore);
|
|
2527
|
+
}
|
|
2528
|
+
setGeometry(geometry) {
|
|
2529
|
+
this.ensureInitialized();
|
|
2530
|
+
mlWasmJSApi_default.setIdCaptureGeometryParams(this.getPipelineType(), geometry.areaDown, geometry.areaUp, geometry.areaIOSPassportUp, geometry.areaIOSPassportDown, geometry.widthIOSUp, geometry.widthIOSDown, geometry.widthDown, geometry.widthUp, geometry.windowOuterWidth, geometry.windowOuterHeight, geometry.windowInnerWidth, geometry.windowInnerHeight);
|
|
2531
|
+
}
|
|
2532
|
+
setSettings(settings) {
|
|
2533
|
+
this.ensureInitialized();
|
|
2534
|
+
mlWasmJSApi_default.setIdCaptureConfigParams(this.getPipelineType(), settings.isFixedMask, settings.isIPhone14OrHigher, settings.idType, settings.blurCheckEnabled, settings.glareCheckEnabled, settings.faceQualityCheckEnabled, settings.iouCheckEnabled);
|
|
2535
|
+
}
|
|
2536
|
+
setModelType(modelType) {
|
|
2537
|
+
this.ensureInitialized();
|
|
2538
|
+
let wasmModelType;
|
|
2539
|
+
switch (modelType) {
|
|
2540
|
+
case "v1":
|
|
2541
|
+
wasmModelType = IdCaptureModelType.IdCaptureV1x;
|
|
2542
|
+
break;
|
|
2543
|
+
case "v2":
|
|
2544
|
+
wasmModelType = IdCaptureModelType.IdCaptureV2x;
|
|
2545
|
+
break;
|
|
2546
|
+
case "v3":
|
|
2547
|
+
wasmModelType = IdCaptureModelType.IdCaptureV3x;
|
|
2548
|
+
break;
|
|
2549
|
+
default: throw new Error(`Unknown model type: ${modelType}`);
|
|
2550
|
+
}
|
|
2551
|
+
mlWasmJSApi_default.setIdCaptureModelType(this.getPipelineType(), wasmModelType);
|
|
2552
|
+
}
|
|
2553
|
+
/**
|
|
2554
|
+
* Processes a frame through the WASM pipeline and stores the result.
|
|
2555
|
+
*/
|
|
2556
|
+
async processFrame(image) {
|
|
2557
|
+
const result = await this.processFrameWasm(image);
|
|
2558
|
+
const pipelineType = this.getPipelineType();
|
|
2559
|
+
if (result && pipelineType === WasmPipelineType.IdBlurGlarePipeline) this.lastProcessResult = result;
|
|
2560
|
+
else this.lastProcessResult = null;
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* Gets the last process result from the most recent frame processing.
|
|
2564
|
+
* @returns The last process result with quad coordinates, or null if not available
|
|
2565
|
+
*/
|
|
2566
|
+
getLastProcessResult() {
|
|
2567
|
+
return this.lastProcessResult;
|
|
2568
|
+
}
|
|
2569
|
+
transformPerspective(canvas, frameRect) {
|
|
2570
|
+
this.ensureInitialized();
|
|
2571
|
+
const originalWidth = canvas.width();
|
|
2572
|
+
const originalHeight = canvas.height();
|
|
2573
|
+
try {
|
|
2574
|
+
const imageData = canvas.getImageData();
|
|
2575
|
+
if (!imageData) return canvas;
|
|
2576
|
+
const transformedCanvas = mlWasmJSApi_default.IdPerspectiveTransform(imageData, frameRect);
|
|
2577
|
+
if (transformedCanvas) {
|
|
2578
|
+
const wrappedCanvas = new IncodeCanvas(transformedCanvas);
|
|
2579
|
+
const wrappedWidth = wrappedCanvas.width();
|
|
2580
|
+
const wrappedHeight = wrappedCanvas.height();
|
|
2581
|
+
if (wrappedWidth === originalWidth && wrappedHeight === originalHeight) {
|
|
2582
|
+
const croppedCanvas = this.cropCanvasToRect(wrappedCanvas, {
|
|
2583
|
+
x: Math.round(frameRect.x),
|
|
2584
|
+
y: Math.round(frameRect.y),
|
|
2585
|
+
w: Math.round(frameRect.w),
|
|
2586
|
+
h: Math.round(frameRect.h)
|
|
2587
|
+
});
|
|
2588
|
+
if (croppedCanvas) return croppedCanvas;
|
|
2589
|
+
}
|
|
2590
|
+
return wrappedCanvas;
|
|
2591
|
+
}
|
|
2592
|
+
return canvas;
|
|
2593
|
+
} catch (_error) {
|
|
2594
|
+
return canvas;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* Crops a canvas to the specified rectangle region.
|
|
2599
|
+
* @param canvas - The canvas to crop
|
|
2600
|
+
* @param rect - The rectangle to crop to (x, y, w, h)
|
|
2601
|
+
* @returns A new IncodeCanvas with the cropped region, or null if cropping fails
|
|
2602
|
+
*/
|
|
2603
|
+
cropCanvasToRect(canvas, rect) {
|
|
2604
|
+
const canvasWidth = canvas.width();
|
|
2605
|
+
const canvasHeight = canvas.height();
|
|
2606
|
+
if (!canvasWidth || !canvasHeight) return null;
|
|
2607
|
+
const x = Math.max(0, Math.min(rect.x, canvasWidth));
|
|
2608
|
+
const y = Math.max(0, Math.min(rect.y, canvasHeight));
|
|
2609
|
+
const w = Math.max(1, Math.min(rect.w, canvasWidth - x));
|
|
2610
|
+
const h = Math.max(1, Math.min(rect.h, canvasHeight - y));
|
|
2611
|
+
const croppedCanvasElement = document.createElement("canvas");
|
|
2612
|
+
croppedCanvasElement.width = w;
|
|
2613
|
+
croppedCanvasElement.height = h;
|
|
2614
|
+
const ctx = croppedCanvasElement.getContext("2d");
|
|
2615
|
+
if (!ctx) return null;
|
|
2616
|
+
ctx.drawImage(canvas.canvas, x, y, w, h, 0, 0, w, h);
|
|
2617
|
+
return new IncodeCanvas(croppedCanvasElement);
|
|
2618
|
+
}
|
|
2619
|
+
/**
|
|
2620
|
+
* Gets the captured canvas (transformed for preview).
|
|
2621
|
+
* @returns The captured canvas, or null if not available
|
|
2622
|
+
*/
|
|
2623
|
+
getCapturedCanvas() {
|
|
2624
|
+
return this.capturedCanvas;
|
|
2625
|
+
}
|
|
2626
|
+
/**
|
|
2627
|
+
* Gets the original captured canvas (full frame for upload).
|
|
2628
|
+
* @returns The original captured canvas, or null if not available
|
|
2629
|
+
*/
|
|
2630
|
+
getOriginalCapturedCanvas() {
|
|
2631
|
+
return this.originalCapturedCanvas;
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Sets the captured canvases (original and transformed).
|
|
2635
|
+
* @param original - The original full-frame canvas (for upload)
|
|
2636
|
+
* @param transformed - The transformed canvas (for preview)
|
|
2637
|
+
*/
|
|
2638
|
+
setCapturedCanvases(original, transformed) {
|
|
2639
|
+
this.originalCapturedCanvas = original;
|
|
2640
|
+
this.capturedCanvas = transformed;
|
|
2641
|
+
}
|
|
2642
|
+
reset() {
|
|
2643
|
+
super.reset();
|
|
2644
|
+
this.lastProcessResult = null;
|
|
2645
|
+
this.capturedCanvas = null;
|
|
2646
|
+
this.originalCapturedCanvas = null;
|
|
2647
|
+
}
|
|
2648
|
+
};
|
|
2649
|
+
|
|
2650
|
+
//#endregion
|
|
2651
|
+
//#region ../infra/src/providers/wasm/WasmUtilProvider.ts
|
|
2652
|
+
var WasmUtilProvider = class WasmUtilProvider extends BaseWasmProvider {
|
|
2653
|
+
static async getInstance() {
|
|
2654
|
+
if (WasmUtilProvider.instance) return WasmUtilProvider.instance;
|
|
2655
|
+
if (WasmUtilProvider.initPromise) return WasmUtilProvider.initPromise;
|
|
2656
|
+
WasmUtilProvider.initPromise = (async () => {
|
|
2657
|
+
const instance = new WasmUtilProvider();
|
|
2658
|
+
await instance.initializeBase({}, "selfie");
|
|
2659
|
+
WasmUtilProvider.instance = instance;
|
|
2660
|
+
return instance;
|
|
2661
|
+
})();
|
|
2662
|
+
return WasmUtilProvider.initPromise;
|
|
2663
|
+
}
|
|
2664
|
+
static resetInstance() {
|
|
2665
|
+
WasmUtilProvider.instance = void 0;
|
|
2666
|
+
WasmUtilProvider.initPromise = void 0;
|
|
2667
|
+
}
|
|
2668
|
+
constructor() {
|
|
2669
|
+
super(void 0);
|
|
2670
|
+
}
|
|
2671
|
+
async initialize(config) {
|
|
2672
|
+
if (this.initialized) return;
|
|
2673
|
+
const pipeline = config.pipelines?.[0] ?? "selfie";
|
|
2674
|
+
await this.initializeBase(config, pipeline);
|
|
2675
|
+
}
|
|
2676
|
+
async processFrame(_image) {
|
|
2677
|
+
throw new Error("WasmUtilProvider does not support frame processing. Use encryptImage() instead.");
|
|
2678
|
+
}
|
|
2679
|
+
encryptImage(image) {
|
|
2680
|
+
this.ensureInitialized();
|
|
2681
|
+
return mlWasmJSApi_default.ens(image);
|
|
2682
|
+
}
|
|
2683
|
+
setSdkVersion(version) {
|
|
2684
|
+
this.ensureInitialized();
|
|
2685
|
+
mlWasmJSApi_default.setSdkVersion(version);
|
|
2686
|
+
}
|
|
2687
|
+
setSdkPlatform(platform) {
|
|
2688
|
+
this.ensureInitialized();
|
|
2689
|
+
mlWasmJSApi_default.setSdkPlatform(platform);
|
|
2690
|
+
}
|
|
2691
|
+
setDeviceInfo(deviceInfo, overrideExisting = true) {
|
|
2692
|
+
this.ensureInitialized();
|
|
2693
|
+
mlWasmJSApi_default.setDeviceInfo(deviceInfo, overrideExisting);
|
|
2694
|
+
}
|
|
2695
|
+
setBrowserInfo(browserInfo, overrideExisting = true) {
|
|
2696
|
+
this.ensureInitialized();
|
|
2697
|
+
mlWasmJSApi_default.setBrowserInfo(browserInfo, overrideExisting);
|
|
2698
|
+
}
|
|
2699
|
+
setCameraInfo(cameraInfo, overrideExisting = true) {
|
|
2700
|
+
this.ensureInitialized();
|
|
2701
|
+
mlWasmJSApi_default.setCameraInfo(cameraInfo, overrideExisting);
|
|
2702
|
+
}
|
|
2703
|
+
setMotionStatus(status) {
|
|
2704
|
+
this.ensureInitialized();
|
|
2705
|
+
mlWasmJSApi_default.setMotionStatus(status);
|
|
2706
|
+
}
|
|
2707
|
+
setBackgroundMode(backgroundMode) {
|
|
2708
|
+
this.ensureInitialized();
|
|
2709
|
+
mlWasmJSApi_default.setBackgroundMode(backgroundMode);
|
|
2710
|
+
}
|
|
2711
|
+
setZc(zc) {
|
|
2712
|
+
this.ensureInitialized();
|
|
2713
|
+
mlWasmJSApi_default.setZc(zc);
|
|
2714
|
+
}
|
|
2715
|
+
setInspectorOpened(opened) {
|
|
2716
|
+
this.ensureInitialized();
|
|
2717
|
+
mlWasmJSApi_default.setInspectorOpened(opened);
|
|
2718
|
+
}
|
|
2719
|
+
getMetadata() {
|
|
2720
|
+
this.ensureInitialized();
|
|
2721
|
+
return mlWasmJSApi_default.getMetadata();
|
|
2722
|
+
}
|
|
2723
|
+
async analyzeFrame(image) {
|
|
2724
|
+
this.ensureInitialized();
|
|
2725
|
+
await mlWasmJSApi_default.analyzeFrame(image);
|
|
2726
|
+
}
|
|
2727
|
+
getCheck() {
|
|
2728
|
+
this.ensureInitialized();
|
|
2729
|
+
return mlWasmJSApi_default.getCheck();
|
|
2730
|
+
}
|
|
2731
|
+
estimatePerformance() {
|
|
2732
|
+
this.ensureInitialized();
|
|
2733
|
+
return mlWasmJSApi_default.estimatePerformance();
|
|
2734
|
+
}
|
|
2735
|
+
isVirtualCamera(label) {
|
|
2736
|
+
this.ensureInitialized();
|
|
2737
|
+
return mlWasmJSApi_default.isVirtualCamera(label);
|
|
2738
|
+
}
|
|
2739
|
+
async prc() {
|
|
2740
|
+
this.ensureInitialized();
|
|
2741
|
+
await mlWasmJSApi_default.prc();
|
|
2742
|
+
}
|
|
2743
|
+
async poc(output) {
|
|
2744
|
+
this.ensureInitialized();
|
|
2745
|
+
await mlWasmJSApi_default.poc(output);
|
|
2746
|
+
}
|
|
2747
|
+
ckvcks(data) {
|
|
2748
|
+
this.ensureInitialized();
|
|
2749
|
+
mlWasmJSApi_default.ckvcks(data);
|
|
2750
|
+
}
|
|
2751
|
+
async getVersions() {
|
|
2752
|
+
this.ensureInitialized();
|
|
2753
|
+
return mlWasmJSApi_default.getVersions();
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
|
|
2757
|
+
//#endregion
|
|
2758
|
+
//#region ../infra/src/wasm/idCaptureDefaults.ts
|
|
2759
|
+
/**
|
|
2760
|
+
* Default WASM thresholds for ID capture quality checks.
|
|
2761
|
+
* Based on legacy pipelinesConfig.ts values.
|
|
2762
|
+
*/
|
|
2763
|
+
const DEFAULT_ID_CAPTURE_THRESHOLDS = {
|
|
2764
|
+
blurThreshold: .2,
|
|
2765
|
+
blurChangeThreshold: .2,
|
|
2766
|
+
glareThreshold: .3,
|
|
2767
|
+
clsThreshold: .98,
|
|
2768
|
+
sideThreshold: .8,
|
|
2769
|
+
iouThreshold: .8,
|
|
2770
|
+
idDetectedTimeout: 1e4,
|
|
2771
|
+
autocaptureTimeout: 5e3,
|
|
2772
|
+
framesAggregationInterval: 3e3,
|
|
2773
|
+
minFaceIdQualityScore: .62
|
|
2774
|
+
};
|
|
2775
|
+
/**
|
|
2776
|
+
* Default model version for ID capture.
|
|
2777
|
+
*/
|
|
2778
|
+
const DEFAULT_ID_CAPTURE_MODEL_VERSION = "v2";
|
|
2779
|
+
|
|
2780
|
+
//#endregion
|
|
2781
|
+
export { createManager as C, isIOS as D, isDesktop as E, isIPhone14OrHigher as O, stopCameraStream as S, isAndroid as T, createHiddenVideoElement as _, FaceDetectionProvider as a, enumerateVideoDevices as b, OpenViduRecordingProvider as c, BrowserTimerProvider as d, BrowserStorageProvider as f, queryCameraPermission as g, StreamCanvasCapture as h, IdCaptureProvider as i, isSafari as k, MotionSensorProvider as l, StreamCanvasProcessingSession as m, DEFAULT_ID_CAPTURE_THRESHOLDS as n, warmupWasm as o, BrowserEnvironmentProvider as p, WasmUtilProvider as r, VisibilityProvider as s, DEFAULT_ID_CAPTURE_MODEL_VERSION as t, LocalRecordingProvider as u, IncodeCanvas as v, createApi_default as w, requestCameraAccess as x, applyTrackConstraints as y };
|