@jaak.ai/stamps 2.0.0 → 2.1.0-dev.2
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/README.md +11 -1
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +759 -623
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/my-component/my-component.css +178 -11
- package/dist/collection/components/my-component/my-component.js +566 -250
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/CameraService.js +156 -344
- package/dist/collection/services/CameraService.js.map +1 -1
- package/dist/collection/services/DetectionService.js +68 -38
- package/dist/collection/services/DetectionService.js.map +1 -1
- package/dist/collection/services/EventBusService.js +1 -1
- package/dist/collection/services/EventBusService.js.map +1 -1
- package/dist/collection/services/ServiceContainer.js +4 -13
- package/dist/collection/services/ServiceContainer.js.map +1 -1
- package/dist/collection/services/interfaces/ICameraService.js.map +1 -1
- package/dist/collection/types/component-types.js.map +1 -1
- package/dist/components/jaak-stamps.js +763 -626
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/jaak-stamps-webcomponent.js +1 -1
- package/dist/esm/jaak-stamps.entry.js +759 -623
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +68 -17
- package/dist/types/components.d.ts +14 -8
- package/dist/types/services/CameraService.d.ts +12 -14
- package/dist/types/services/DetectionService.d.ts +10 -3
- package/dist/types/services/ServiceContainer.d.ts +1 -2
- package/dist/types/services/interfaces/ICameraService.d.ts +3 -12
- package/dist/types/types/component-types.d.ts +3 -0
- package/package.json +2 -2
- package/dist/collection/services/LoggerService.js +0 -40
- package/dist/collection/services/LoggerService.js.map +0 -1
- package/dist/collection/services/interfaces/ILogger.js +0 -2
- package/dist/collection/services/interfaces/ILogger.js.map +0 -1
- package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js.map +0 -1
- package/dist/types/services/LoggerService.d.ts +0 -12
- package/dist/types/services/interfaces/ILogger.d.ts +0 -8
|
@@ -1,45 +1,5 @@
|
|
|
1
1
|
import { p as proxyCustomElement, H, c as createEvent, h } from './index.js';
|
|
2
2
|
|
|
3
|
-
class LoggerService {
|
|
4
|
-
debugMode;
|
|
5
|
-
constructor(debug = false) {
|
|
6
|
-
this.debugMode = debug;
|
|
7
|
-
}
|
|
8
|
-
setDebugMode(debug) {
|
|
9
|
-
this.debugMode = debug;
|
|
10
|
-
}
|
|
11
|
-
info(...args) {
|
|
12
|
-
if (this.debugMode) {
|
|
13
|
-
console.log(`[JAAK-STAMPS] [INFO] [${new Date().toLocaleTimeString()}]`, ...args);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
warn(...args) {
|
|
17
|
-
if (this.debugMode) {
|
|
18
|
-
console.warn(`[JAAK-STAMPS] [WARN] [${new Date().toLocaleTimeString()}]`, ...args);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
error(...args) {
|
|
22
|
-
if (this.debugMode) {
|
|
23
|
-
console.error(`[JAAK-STAMPS] [ERROR] [${new Date().toLocaleTimeString()}]`, ...args);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
debug(...args) {
|
|
27
|
-
if (this.debugMode) {
|
|
28
|
-
console.debug(`[JAAK-STAMPS] [DEBUG] [${new Date().toLocaleTimeString()}]`, ...args);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
state(state, data) {
|
|
32
|
-
if (this.debugMode) {
|
|
33
|
-
console.log(`[JAAK-STAMPS] [STATE] [${new Date().toLocaleTimeString()}] ${state}`, data || '');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
performance(operation, duration) {
|
|
37
|
-
if (this.debugMode) {
|
|
38
|
-
console.log(`[JAAK-STAMPS] [PERF] [${new Date().toLocaleTimeString()}] ${operation}: ${duration}ms`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
3
|
class EventBusService {
|
|
44
4
|
events = new Map();
|
|
45
5
|
on(event, callback) {
|
|
@@ -65,7 +25,7 @@ class EventBusService {
|
|
|
65
25
|
callback(data);
|
|
66
26
|
}
|
|
67
27
|
catch (error) {
|
|
68
|
-
|
|
28
|
+
// Silently handle callback errors
|
|
69
29
|
}
|
|
70
30
|
});
|
|
71
31
|
}
|
|
@@ -192,16 +152,20 @@ class StateManagerService {
|
|
|
192
152
|
}
|
|
193
153
|
|
|
194
154
|
class CameraService {
|
|
195
|
-
logger;
|
|
196
155
|
eventBus;
|
|
197
156
|
availableCameras = [];
|
|
198
157
|
selectedCameraId = null;
|
|
199
158
|
deviceType = 'desktop';
|
|
200
159
|
preferredCameraFacing = null;
|
|
201
160
|
preferredCamera = 'auto';
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
161
|
+
// Optimization: Static caches for performance
|
|
162
|
+
static cameraCapabilitiesCache = new Map();
|
|
163
|
+
static deviceEnumerationCache = null;
|
|
164
|
+
static CACHE_DURATION = 30000; // 30 seconds
|
|
165
|
+
// Stream management for optimization
|
|
166
|
+
currentStreamPromise;
|
|
167
|
+
lastStreamConstraints;
|
|
168
|
+
constructor(eventBus, preferredCamera = 'auto') {
|
|
205
169
|
this.eventBus = eventBus;
|
|
206
170
|
this.preferredCamera = preferredCamera;
|
|
207
171
|
}
|
|
@@ -218,18 +182,21 @@ class CameraService {
|
|
|
218
182
|
else {
|
|
219
183
|
this.deviceType = 'desktop';
|
|
220
184
|
}
|
|
221
|
-
this.logger.state('DISPOSITIVO_DETECTADO', {
|
|
222
|
-
deviceType: this.deviceType,
|
|
223
|
-
userAgent: navigator.userAgent,
|
|
224
|
-
screenDimensions: { width: window.innerWidth, height: window.innerHeight }
|
|
225
|
-
});
|
|
226
185
|
return this.deviceType;
|
|
227
186
|
}
|
|
228
187
|
async enumerateDevices() {
|
|
229
188
|
try {
|
|
189
|
+
// Check cache first for optimization
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
if (CameraService.deviceEnumerationCache &&
|
|
192
|
+
(now - CameraService.deviceEnumerationCache.timestamp) < CameraService.CACHE_DURATION) {
|
|
193
|
+
this.availableCameras = CameraService.deviceEnumerationCache.devices;
|
|
194
|
+
await this.setInitialCameraPreference();
|
|
195
|
+
this.eventBus.emit('camera-changed', this.selectedCameraId);
|
|
196
|
+
return this.availableCameras;
|
|
197
|
+
}
|
|
230
198
|
const permissionStatus = await this.checkCameraPermission();
|
|
231
199
|
if (permissionStatus === 'denied') {
|
|
232
|
-
this.logger.error('Permiso de cámara denegado por el usuario');
|
|
233
200
|
return [];
|
|
234
201
|
}
|
|
235
202
|
if (permissionStatus === 'prompt') {
|
|
@@ -238,24 +205,16 @@ class CameraService {
|
|
|
238
205
|
}
|
|
239
206
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
240
207
|
this.availableCameras = devices.filter(device => device.kind === 'videoinput');
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
248
|
-
this.setInitialCameraPreference();
|
|
249
|
-
this.logger.state('PREFERENCIA_INICIAL_APLICADA', {
|
|
250
|
-
selectedCameraId: this.selectedCameraId,
|
|
251
|
-
preferredCameraFacing: this.preferredCameraFacing,
|
|
252
|
-
preferredCamera: this.preferredCamera
|
|
253
|
-
});
|
|
208
|
+
// Cache the results for optimization
|
|
209
|
+
CameraService.deviceEnumerationCache = {
|
|
210
|
+
devices: this.availableCameras,
|
|
211
|
+
timestamp: now
|
|
212
|
+
};
|
|
213
|
+
await this.setInitialCameraPreference();
|
|
254
214
|
this.eventBus.emit('camera-changed', this.selectedCameraId);
|
|
255
215
|
return this.availableCameras;
|
|
256
216
|
}
|
|
257
217
|
catch (error) {
|
|
258
|
-
this.logger.error('Error al enumerar cámaras disponibles:', error);
|
|
259
218
|
this.handleCameraPermissionError(error);
|
|
260
219
|
return [];
|
|
261
220
|
}
|
|
@@ -276,8 +235,6 @@ class CameraService {
|
|
|
276
235
|
}
|
|
277
236
|
this.selectedCameraId = cameraId;
|
|
278
237
|
this.updatePreferredFacing(camera);
|
|
279
|
-
this.isManuallySelected = true; // Mark as manually selected
|
|
280
|
-
this.savePreference();
|
|
281
238
|
this.eventBus.emit('camera-changed', cameraId);
|
|
282
239
|
}
|
|
283
240
|
getPreferredFacing() {
|
|
@@ -286,14 +243,20 @@ class CameraService {
|
|
|
286
243
|
async setupCamera(constraints) {
|
|
287
244
|
try {
|
|
288
245
|
const finalConstraints = constraints || await this.getMaxResolution();
|
|
289
|
-
|
|
246
|
+
// Optimization: Check if we can reuse existing stream with same constraints
|
|
247
|
+
if (this.currentStreamPromise && this.constraintsEqual(finalConstraints, this.lastStreamConstraints)) {
|
|
248
|
+
return await this.currentStreamPromise;
|
|
249
|
+
}
|
|
250
|
+
// Create new stream promise for lazy loading
|
|
251
|
+
this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
|
|
290
252
|
video: finalConstraints,
|
|
291
253
|
audio: false
|
|
292
254
|
});
|
|
255
|
+
this.lastStreamConstraints = finalConstraints;
|
|
256
|
+
const stream = await this.currentStreamPromise;
|
|
293
257
|
return stream;
|
|
294
258
|
}
|
|
295
259
|
catch (error) {
|
|
296
|
-
this.logger.error('Error en setupCamera, intentando con restricciones básicas:', error);
|
|
297
260
|
// Fallback to basic constraints if getMaxResolution fails
|
|
298
261
|
const basicConstraints = {
|
|
299
262
|
width: { ideal: 1280 },
|
|
@@ -303,65 +266,21 @@ class CameraService {
|
|
|
303
266
|
if (this.deviceType !== 'desktop' && this.preferredCameraFacing) {
|
|
304
267
|
basicConstraints.facingMode = this.preferredCameraFacing;
|
|
305
268
|
}
|
|
306
|
-
this.
|
|
307
|
-
return await navigator.mediaDevices.getUserMedia({
|
|
269
|
+
this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
|
|
308
270
|
video: basicConstraints,
|
|
309
271
|
audio: false
|
|
310
272
|
});
|
|
273
|
+
this.lastStreamConstraints = basicConstraints;
|
|
274
|
+
return await this.currentStreamPromise;
|
|
311
275
|
}
|
|
312
276
|
}
|
|
313
277
|
async switchCamera(cameraId) {
|
|
314
278
|
const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
|
|
315
279
|
if (!selectedCamera) {
|
|
316
|
-
this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
|
|
317
280
|
await this.enumerateDevices();
|
|
318
281
|
return;
|
|
319
282
|
}
|
|
320
283
|
await this.setSelectedCamera(cameraId);
|
|
321
|
-
this.logger.state('CAMARA_CAMBIADA', {
|
|
322
|
-
label: selectedCamera.label,
|
|
323
|
-
deviceId: selectedCamera.deviceId,
|
|
324
|
-
isManuallySelected: this.isManuallySelected
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
async flipToNextCamera() {
|
|
328
|
-
if (!this.isMultipleCamerasAvailable())
|
|
329
|
-
return;
|
|
330
|
-
const currentIndex = this.availableCameras.findIndex(camera => camera.deviceId === this.selectedCameraId);
|
|
331
|
-
const nextIndex = (currentIndex + 1) % this.availableCameras.length;
|
|
332
|
-
const nextCamera = this.availableCameras[nextIndex];
|
|
333
|
-
await this.switchCamera(nextCamera.deviceId);
|
|
334
|
-
}
|
|
335
|
-
savePreference() {
|
|
336
|
-
try {
|
|
337
|
-
const preference = {
|
|
338
|
-
cameraId: this.selectedCameraId,
|
|
339
|
-
facing: this.preferredCameraFacing,
|
|
340
|
-
timestamp: Date.now()
|
|
341
|
-
};
|
|
342
|
-
localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
|
|
343
|
-
this.logger.state('PREFERENCIA_CAMARA_GUARDADA', preference);
|
|
344
|
-
}
|
|
345
|
-
catch (error) {
|
|
346
|
-
this.logger.warn('Error al guardar preferencia de cámara:', error);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
loadPreference() {
|
|
350
|
-
try {
|
|
351
|
-
const saved = localStorage.getItem('jaak-stamps-camera-preference');
|
|
352
|
-
if (saved) {
|
|
353
|
-
const preference = JSON.parse(saved);
|
|
354
|
-
const isStillAvailable = this.availableCameras.some(camera => camera.deviceId === preference.cameraId);
|
|
355
|
-
if (isStillAvailable) {
|
|
356
|
-
this.selectedCameraId = preference.cameraId;
|
|
357
|
-
this.preferredCameraFacing = preference.facing;
|
|
358
|
-
this.logger.state('PREFERENCIA_CAMARA_CARGADA', preference);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
catch (error) {
|
|
363
|
-
this.logger.warn('Error al cargar preferencia de cámara:', error);
|
|
364
|
-
}
|
|
365
284
|
}
|
|
366
285
|
isRearCamera(stream) {
|
|
367
286
|
const videoTrack = stream.getVideoTracks()[0];
|
|
@@ -370,13 +289,18 @@ class CameraService {
|
|
|
370
289
|
const settings = videoTrack.getSettings();
|
|
371
290
|
return settings.facingMode === 'environment';
|
|
372
291
|
}
|
|
373
|
-
getCameraInfo() {
|
|
374
|
-
|
|
375
|
-
|
|
292
|
+
async getCameraInfo() {
|
|
293
|
+
const availableCamerasWithAutofocus = await Promise.all(this.availableCameras.map(async (camera) => {
|
|
294
|
+
const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
|
|
295
|
+
return {
|
|
376
296
|
id: camera.deviceId,
|
|
377
297
|
label: camera.label || 'Unknown Camera',
|
|
378
|
-
selected: camera.deviceId === this.selectedCameraId
|
|
379
|
-
|
|
298
|
+
selected: camera.deviceId === this.selectedCameraId,
|
|
299
|
+
hasAutofocus
|
|
300
|
+
};
|
|
301
|
+
}));
|
|
302
|
+
return {
|
|
303
|
+
availableCameras: availableCamerasWithAutofocus,
|
|
380
304
|
selectedCameraId: this.selectedCameraId,
|
|
381
305
|
deviceType: this.deviceType,
|
|
382
306
|
isMultipleCamerasAvailable: this.isMultipleCamerasAvailable(),
|
|
@@ -392,7 +316,6 @@ class CameraService {
|
|
|
392
316
|
return permission.state;
|
|
393
317
|
}
|
|
394
318
|
catch (error) {
|
|
395
|
-
this.logger.warn('No se pudo verificar permisos de cámara:', error);
|
|
396
319
|
return 'prompt';
|
|
397
320
|
}
|
|
398
321
|
}
|
|
@@ -400,35 +323,48 @@ class CameraService {
|
|
|
400
323
|
this.availableCameras = [];
|
|
401
324
|
this.eventBus.emit('error', new Error(`Camera permission error: ${error.message}`));
|
|
402
325
|
}
|
|
403
|
-
setInitialCameraPreference() {
|
|
326
|
+
async setInitialCameraPreference() {
|
|
404
327
|
if (this.availableCameras.length === 0)
|
|
405
328
|
return;
|
|
406
|
-
// Clear any existing localStorage preferences to ensure fresh start
|
|
407
|
-
localStorage.removeItem('jaak-stamps-camera-preference');
|
|
408
|
-
this.logger.state('APLICANDO_PREFERENCIA_INICIAL', {
|
|
409
|
-
preferredCamera: this.preferredCamera,
|
|
410
|
-
availableCamerasCount: this.availableCameras.length
|
|
411
|
-
});
|
|
412
|
-
this.isManuallySelected = false; // Reset manual selection flag
|
|
413
329
|
if (this.preferredCamera === 'front') {
|
|
414
|
-
this.selectFrontCamera();
|
|
330
|
+
await this.selectFrontCamera();
|
|
415
331
|
}
|
|
416
332
|
else if (this.preferredCamera === 'back') {
|
|
417
|
-
this.selectBackCamera();
|
|
333
|
+
await this.selectBackCamera();
|
|
418
334
|
}
|
|
419
335
|
else {
|
|
420
|
-
this.selectAutoCamera();
|
|
336
|
+
await this.selectAutoCamera();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async checkCameraAutofocus(deviceId) {
|
|
340
|
+
try {
|
|
341
|
+
// Check cache first for optimization
|
|
342
|
+
if (CameraService.cameraCapabilitiesCache.has(deviceId)) {
|
|
343
|
+
const cached = CameraService.cameraCapabilitiesCache.get(deviceId);
|
|
344
|
+
return typeof cached === 'boolean' ? cached : false;
|
|
345
|
+
}
|
|
346
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
347
|
+
video: { deviceId: { exact: deviceId } }
|
|
348
|
+
});
|
|
349
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
350
|
+
const capabilities = videoTrack.getCapabilities();
|
|
351
|
+
stream.getTracks().forEach(track => track.stop());
|
|
352
|
+
const hasFocusMode = capabilities.focusMode && Array.isArray(capabilities.focusMode) && capabilities.focusMode.length > 0;
|
|
353
|
+
const hasAutofocus = hasFocusMode && (capabilities.focusMode.includes('continuous') ||
|
|
354
|
+
capabilities.focusMode.includes('auto'));
|
|
355
|
+
// Cache the result for future use
|
|
356
|
+
CameraService.cameraCapabilitiesCache.set(deviceId, hasAutofocus);
|
|
357
|
+
return hasAutofocus;
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
// Cache negative result to avoid repeated failures
|
|
361
|
+
CameraService.cameraCapabilitiesCache.set(deviceId, false);
|
|
362
|
+
return false;
|
|
421
363
|
}
|
|
422
364
|
}
|
|
423
|
-
selectFrontCamera() {
|
|
365
|
+
async selectFrontCamera() {
|
|
424
366
|
this.preferredCameraFacing = 'user';
|
|
425
|
-
this.
|
|
426
|
-
availableCameras: this.availableCameras.map(cam => ({
|
|
427
|
-
id: cam.deviceId,
|
|
428
|
-
label: cam.label
|
|
429
|
-
}))
|
|
430
|
-
});
|
|
431
|
-
const frontCamera = this.availableCameras.find(camera => {
|
|
367
|
+
const frontCameras = this.availableCameras.filter(camera => {
|
|
432
368
|
const label = camera.label.toLowerCase();
|
|
433
369
|
return label.includes('front') ||
|
|
434
370
|
label.includes('user') ||
|
|
@@ -436,24 +372,24 @@ class CameraService {
|
|
|
436
372
|
label.includes('frontal') ||
|
|
437
373
|
(!label.includes('back') && !label.includes('rear') && !label.includes('environment'));
|
|
438
374
|
});
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
375
|
+
let selectedCamera = null;
|
|
376
|
+
if (frontCameras.length > 0) {
|
|
377
|
+
for (const camera of frontCameras) {
|
|
378
|
+
const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
|
|
379
|
+
if (hasAutofocus) {
|
|
380
|
+
selectedCamera = camera;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (!selectedCamera) {
|
|
385
|
+
selectedCamera = frontCameras[0];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
|
|
446
389
|
}
|
|
447
|
-
selectBackCamera() {
|
|
390
|
+
async selectBackCamera() {
|
|
448
391
|
this.preferredCameraFacing = 'environment';
|
|
449
|
-
this.
|
|
450
|
-
availableCameras: this.availableCameras.map(cam => ({
|
|
451
|
-
id: cam.deviceId,
|
|
452
|
-
label: cam.label
|
|
453
|
-
}))
|
|
454
|
-
});
|
|
455
|
-
// Try to find rear camera with multiple detection methods
|
|
456
|
-
let backCamera = this.availableCameras.find(camera => {
|
|
392
|
+
const backCameras = this.availableCameras.filter(camera => {
|
|
457
393
|
const label = camera.label.toLowerCase();
|
|
458
394
|
return label.includes('back') ||
|
|
459
395
|
label.includes('rear') ||
|
|
@@ -461,34 +397,33 @@ class CameraService {
|
|
|
461
397
|
label.includes('trasera') ||
|
|
462
398
|
label.includes('posterior');
|
|
463
399
|
});
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
400
|
+
let selectedCamera = null;
|
|
401
|
+
if (backCameras.length > 0) {
|
|
402
|
+
for (const camera of backCameras) {
|
|
403
|
+
const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
|
|
404
|
+
if (hasAutofocus) {
|
|
405
|
+
selectedCamera = camera;
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (!selectedCamera) {
|
|
410
|
+
selectedCamera = backCameras[0];
|
|
411
|
+
}
|
|
471
412
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
413
|
+
else if (this.availableCameras.length > 1) {
|
|
414
|
+
const lastCamera = this.availableCameras[this.availableCameras.length - 1];
|
|
415
|
+
const hasAutofocus = await this.checkCameraAutofocus(lastCamera.deviceId);
|
|
416
|
+
selectedCamera = hasAutofocus ? lastCamera : this.availableCameras[this.availableCameras.length - 1];
|
|
417
|
+
}
|
|
418
|
+
this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
|
|
478
419
|
}
|
|
479
|
-
selectAutoCamera() {
|
|
420
|
+
async selectAutoCamera() {
|
|
480
421
|
if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
|
|
481
|
-
this.selectBackCamera();
|
|
482
|
-
this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
|
|
422
|
+
await this.selectBackCamera();
|
|
483
423
|
}
|
|
484
424
|
else {
|
|
485
425
|
// For desktop, prefer front camera (usually built-in webcam)
|
|
486
|
-
this.selectFrontCamera();
|
|
487
|
-
this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', {
|
|
488
|
-
type: 'front',
|
|
489
|
-
label: this.availableCameras[0].label,
|
|
490
|
-
deviceId: this.selectedCameraId
|
|
491
|
-
});
|
|
426
|
+
await this.selectFrontCamera();
|
|
492
427
|
}
|
|
493
428
|
}
|
|
494
429
|
updatePreferredFacing(camera) {
|
|
@@ -504,40 +439,19 @@ class CameraService {
|
|
|
504
439
|
await this.detectDeviceType();
|
|
505
440
|
}
|
|
506
441
|
const videoConstraints = {};
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
preferredCameraFacing: this.preferredCameraFacing,
|
|
510
|
-
preferredCamera: this.preferredCamera,
|
|
511
|
-
deviceType: this.deviceType,
|
|
512
|
-
isManuallySelected: this.isManuallySelected
|
|
513
|
-
});
|
|
514
|
-
if (this.isManuallySelected && this.selectedCameraId) {
|
|
515
|
-
// When manually selected, use deviceId for exact camera selection
|
|
442
|
+
// Priority 1: Always use deviceId if available (ensures specific camera with autofocus)
|
|
443
|
+
if (this.selectedCameraId) {
|
|
516
444
|
videoConstraints.deviceId = { exact: this.selectedCameraId };
|
|
517
|
-
this.logger.state('USANDO_CAMARA_MANUAL', {
|
|
518
|
-
deviceId: this.selectedCameraId
|
|
519
|
-
});
|
|
520
445
|
}
|
|
446
|
+
// Priority 2: Use facingMode as fallback when no specific camera is selected
|
|
521
447
|
else if (this.preferredCameraFacing) {
|
|
522
|
-
// Use facingMode for initial preference-based selection
|
|
523
|
-
// Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
|
|
524
448
|
const facingConstraint = this.deviceType === 'desktop'
|
|
525
449
|
? { ideal: this.preferredCameraFacing }
|
|
526
450
|
: { exact: this.preferredCameraFacing };
|
|
527
451
|
videoConstraints.facingMode = facingConstraint;
|
|
528
|
-
this.logger.state('USANDO_FACING_CONSTRAINT', {
|
|
529
|
-
facingMode: this.preferredCameraFacing,
|
|
530
|
-
constraintType: this.deviceType === 'desktop' ? 'ideal' : 'exact'
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
else if (this.selectedCameraId) {
|
|
534
|
-
videoConstraints.deviceId = { exact: this.selectedCameraId };
|
|
535
|
-
this.logger.state('USANDO_CAMARA_SELECCIONADA', {
|
|
536
|
-
deviceId: this.selectedCameraId
|
|
537
|
-
});
|
|
538
452
|
}
|
|
453
|
+
// Priority 3: Fallback to preferredCamera setting
|
|
539
454
|
else {
|
|
540
|
-
// Use preferredCamera setting to determine default facing mode
|
|
541
455
|
if (this.preferredCamera === 'front') {
|
|
542
456
|
videoConstraints.facingMode = "user";
|
|
543
457
|
}
|
|
@@ -545,24 +459,25 @@ class CameraService {
|
|
|
545
459
|
videoConstraints.facingMode = "environment";
|
|
546
460
|
}
|
|
547
461
|
else {
|
|
548
|
-
// Auto mode: use environment for mobile/tablet, user for desktop
|
|
549
462
|
videoConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
|
|
550
463
|
}
|
|
551
|
-
this.logger.state('USANDO_PREFERENCIA_CONFIGURADA', {
|
|
552
|
-
facingMode: videoConstraints.facingMode,
|
|
553
|
-
preferredCamera: this.preferredCamera,
|
|
554
|
-
deviceType: this.deviceType
|
|
555
|
-
});
|
|
556
464
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
465
|
+
// Optimization: Use cached capabilities if available
|
|
466
|
+
let capabilities;
|
|
467
|
+
if (this.selectedCameraId && CameraService.cameraCapabilitiesCache.has(this.selectedCameraId + '_caps')) {
|
|
468
|
+
capabilities = JSON.parse(CameraService.cameraCapabilitiesCache.get(this.selectedCameraId + '_caps'));
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
const tempStream = await navigator.mediaDevices.getUserMedia({ video: videoConstraints });
|
|
472
|
+
const videoTrack = tempStream.getVideoTracks()[0];
|
|
473
|
+
capabilities = videoTrack.getCapabilities();
|
|
474
|
+
tempStream.getTracks().forEach(track => track.stop());
|
|
475
|
+
// Cache capabilities for future use
|
|
476
|
+
if (this.selectedCameraId) {
|
|
477
|
+
CameraService.cameraCapabilitiesCache.set(this.selectedCameraId + '_caps', JSON.stringify(capabilities));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
563
480
|
const constraints = { ...videoConstraints };
|
|
564
|
-
// Enhanced focus and image quality settings
|
|
565
|
-
this.applyAdvancedCameraSettings(constraints, capabilities);
|
|
566
481
|
if (capabilities.width && capabilities.height) {
|
|
567
482
|
const maxWidth = Math.min(capabilities.width.max, 1920);
|
|
568
483
|
const maxHeight = Math.min(capabilities.height.max, 1080);
|
|
@@ -576,15 +491,20 @@ class CameraService {
|
|
|
576
491
|
constraints.height = { ideal: maxHeight };
|
|
577
492
|
}
|
|
578
493
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
494
|
+
// Configure autofocus if camera has the capability
|
|
495
|
+
const capabilitiesAny = capabilities;
|
|
496
|
+
if (capabilitiesAny.focusMode && Array.isArray(capabilitiesAny.focusMode)) {
|
|
497
|
+
if (capabilitiesAny.focusMode.includes('continuous')) {
|
|
498
|
+
constraints.focusMode = 'continuous';
|
|
499
|
+
}
|
|
500
|
+
else if (capabilitiesAny.focusMode.includes('auto')) {
|
|
501
|
+
constraints.focusMode = 'auto';
|
|
502
|
+
}
|
|
503
|
+
}
|
|
583
504
|
return constraints;
|
|
584
505
|
}
|
|
585
506
|
catch (err) {
|
|
586
|
-
|
|
587
|
-
// Ensure device type is detected before determining constraints
|
|
507
|
+
// Fallback logic with minimal temporary streams
|
|
588
508
|
if (!this.deviceType || this.deviceType === 'desktop') {
|
|
589
509
|
await this.detectDeviceType();
|
|
590
510
|
}
|
|
@@ -593,25 +513,16 @@ class CameraService {
|
|
|
593
513
|
width: { ideal: isTablet ? 1280 : 1920 },
|
|
594
514
|
height: { ideal: isTablet ? 720 : 1080 }
|
|
595
515
|
};
|
|
596
|
-
|
|
597
|
-
this.applyBasicFocusSettings(fallbackConstraints);
|
|
598
|
-
if (this.isManuallySelected && this.selectedCameraId) {
|
|
599
|
-
// When manually selected, use deviceId for exact camera selection
|
|
516
|
+
if (this.selectedCameraId) {
|
|
600
517
|
fallbackConstraints.deviceId = { exact: this.selectedCameraId };
|
|
601
518
|
}
|
|
602
519
|
else if (this.preferredCameraFacing) {
|
|
603
|
-
// Use facingMode for initial preference-based selection
|
|
604
|
-
// Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
|
|
605
520
|
const facingConstraint = this.deviceType === 'desktop'
|
|
606
521
|
? { ideal: this.preferredCameraFacing }
|
|
607
522
|
: { exact: this.preferredCameraFacing };
|
|
608
523
|
fallbackConstraints.facingMode = facingConstraint;
|
|
609
524
|
}
|
|
610
|
-
else if (this.selectedCameraId) {
|
|
611
|
-
fallbackConstraints.deviceId = { exact: this.selectedCameraId };
|
|
612
|
-
}
|
|
613
525
|
else {
|
|
614
|
-
// Use preferredCamera setting to determine default facing mode
|
|
615
526
|
if (this.preferredCamera === 'front') {
|
|
616
527
|
fallbackConstraints.facingMode = "user";
|
|
617
528
|
}
|
|
@@ -619,165 +530,26 @@ class CameraService {
|
|
|
619
530
|
fallbackConstraints.facingMode = "environment";
|
|
620
531
|
}
|
|
621
532
|
else {
|
|
622
|
-
// Auto mode: use environment for mobile/tablet, user for desktop
|
|
623
533
|
fallbackConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
|
|
624
534
|
}
|
|
625
535
|
}
|
|
626
536
|
return fallbackConstraints;
|
|
627
537
|
}
|
|
628
538
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
if (capabilities.frameRate) {
|
|
633
|
-
constraints.frameRate = {
|
|
634
|
-
ideal: 30,
|
|
635
|
-
min: 15,
|
|
636
|
-
max: 60
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
// Width and height constraints for optimal resolution
|
|
640
|
-
if (capabilities.width && capabilities.height) {
|
|
641
|
-
constraints.width = {
|
|
642
|
-
ideal: Math.min(1920, capabilities.width.max || 1920),
|
|
643
|
-
min: 640
|
|
644
|
-
};
|
|
645
|
-
constraints.height = {
|
|
646
|
-
ideal: Math.min(1080, capabilities.height.max || 1080),
|
|
647
|
-
min: 480
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
// Apply enhanced constraints through advanced array for better browser compatibility
|
|
651
|
-
const advancedConstraints = [];
|
|
652
|
-
// Try to set optimal settings through advanced constraints
|
|
653
|
-
const advanced = {};
|
|
654
|
-
// Focus mode - use 'any' type to bypass TypeScript limitations
|
|
655
|
-
if (capabilities.focusMode) {
|
|
656
|
-
if (capabilities.focusMode.includes('continuous')) {
|
|
657
|
-
advanced.focusMode = 'continuous';
|
|
658
|
-
}
|
|
659
|
-
else if (capabilities.focusMode.includes('single-shot')) {
|
|
660
|
-
advanced.focusMode = 'single-shot';
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
// Focus distance for close objects
|
|
664
|
-
if (capabilities.focusDistance) {
|
|
665
|
-
advanced.focusDistance = {
|
|
666
|
-
ideal: 0.4, // 40cm - optimal for document capture
|
|
667
|
-
min: 0.2,
|
|
668
|
-
max: 1.0
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
// Zoom control
|
|
672
|
-
if (capabilities.zoom) {
|
|
673
|
-
advanced.zoom = {
|
|
674
|
-
ideal: 1.0,
|
|
675
|
-
min: capabilities.zoom.min,
|
|
676
|
-
max: Math.min(capabilities.zoom.max, 3.0)
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
// Add advanced constraints if any were set
|
|
680
|
-
if (Object.keys(advanced).length > 0) {
|
|
681
|
-
advancedConstraints.push(advanced);
|
|
682
|
-
}
|
|
683
|
-
if (advancedConstraints.length > 0) {
|
|
684
|
-
constraints.advanced = advancedConstraints;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
applyBasicFocusSettings(constraints) {
|
|
688
|
-
// Apply basic focus settings when capabilities are not available
|
|
689
|
-
// Use advanced constraints for non-standard properties
|
|
690
|
-
const advanced = {
|
|
691
|
-
focusMode: 'continuous',
|
|
692
|
-
exposureMode: 'continuous',
|
|
693
|
-
whiteBalanceMode: 'continuous'
|
|
694
|
-
};
|
|
695
|
-
if (!constraints.advanced) {
|
|
696
|
-
constraints.advanced = [];
|
|
697
|
-
}
|
|
698
|
-
constraints.advanced.push(advanced);
|
|
699
|
-
constraints.frameRate = { ideal: 30 };
|
|
700
|
-
}
|
|
701
|
-
getCapabilitiesSummary(capabilities) {
|
|
702
|
-
return {
|
|
703
|
-
hasAutoFocus: !!capabilities.focusMode,
|
|
704
|
-
hasFocusDistance: !!capabilities.focusDistance,
|
|
705
|
-
hasExposureControl: !!capabilities.exposureMode,
|
|
706
|
-
hasWhiteBalance: !!capabilities.whiteBalanceMode,
|
|
707
|
-
hasZoom: !!capabilities.zoom,
|
|
708
|
-
hasTorch: capabilities.torch !== undefined,
|
|
709
|
-
hasImageControls: !!(capabilities.brightness || capabilities.contrast || capabilities.saturation || capabilities.sharpness),
|
|
710
|
-
resolutionRange: capabilities.width && capabilities.height ?
|
|
711
|
-
`${capabilities.width.min}x${capabilities.height.min} - ${capabilities.width.max}x${capabilities.height.max}` : 'unknown'
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
// New method to enable torch/flash for better illumination
|
|
715
|
-
async setTorchEnabled(enabled, stream) {
|
|
716
|
-
try {
|
|
717
|
-
if (!stream) {
|
|
718
|
-
this.logger.warn('No hay stream disponible para controlar el flash');
|
|
719
|
-
return false;
|
|
720
|
-
}
|
|
721
|
-
const videoTrack = stream.getVideoTracks()[0];
|
|
722
|
-
if (!videoTrack) {
|
|
723
|
-
this.logger.warn('No hay track de video disponible');
|
|
724
|
-
return false;
|
|
725
|
-
}
|
|
726
|
-
const capabilities = videoTrack.getCapabilities();
|
|
727
|
-
if (capabilities.torch === undefined) {
|
|
728
|
-
this.logger.warn('El dispositivo no soporta control de flash');
|
|
729
|
-
return false;
|
|
730
|
-
}
|
|
731
|
-
await videoTrack.applyConstraints({
|
|
732
|
-
advanced: [{ torch: enabled }]
|
|
733
|
-
});
|
|
734
|
-
this.logger.state('FLASH_CONTROLADO', { enabled });
|
|
735
|
-
return true;
|
|
736
|
-
}
|
|
737
|
-
catch (error) {
|
|
738
|
-
this.logger.error('Error al controlar el flash:', error);
|
|
539
|
+
// Optimization helper methods
|
|
540
|
+
constraintsEqual(a, b) {
|
|
541
|
+
if (!a || !b)
|
|
739
542
|
return false;
|
|
740
|
-
|
|
543
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
741
544
|
}
|
|
742
|
-
//
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (!videoTrack) {
|
|
751
|
-
this.logger.warn('No hay track de video disponible');
|
|
752
|
-
return false;
|
|
753
|
-
}
|
|
754
|
-
const capabilities = videoTrack.getCapabilities();
|
|
755
|
-
if (!capabilities.focusMode || !capabilities.focusMode.includes('single-shot')) {
|
|
756
|
-
this.logger.warn('El dispositivo no soporta enfoque manual');
|
|
757
|
-
return false;
|
|
758
|
-
}
|
|
759
|
-
// Trigger single-shot focus
|
|
760
|
-
await videoTrack.applyConstraints({
|
|
761
|
-
advanced: [{ focusMode: 'single-shot' }]
|
|
762
|
-
});
|
|
763
|
-
// Return to continuous focus after a short delay
|
|
764
|
-
setTimeout(async () => {
|
|
765
|
-
try {
|
|
766
|
-
await videoTrack.applyConstraints({
|
|
767
|
-
advanced: [{ focusMode: 'continuous' }]
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
catch (error) {
|
|
771
|
-
this.logger.warn('Error al volver a enfoque continuo:', error);
|
|
772
|
-
}
|
|
773
|
-
}, 1000);
|
|
774
|
-
this.logger.state('ENFOQUE_MANUAL_ACTIVADO', { x, y });
|
|
775
|
-
return true;
|
|
776
|
-
}
|
|
777
|
-
catch (error) {
|
|
778
|
-
this.logger.error('Error al enfocar manualmente:', error);
|
|
779
|
-
return false;
|
|
780
|
-
}
|
|
545
|
+
// Cleanup method for optimization
|
|
546
|
+
static clearCaches() {
|
|
547
|
+
CameraService.cameraCapabilitiesCache.clear();
|
|
548
|
+
CameraService.deviceEnumerationCache = null;
|
|
549
|
+
}
|
|
550
|
+
// Method to invalidate device cache when needed
|
|
551
|
+
invalidateDeviceCache() {
|
|
552
|
+
CameraService.deviceEnumerationCache = null;
|
|
781
553
|
}
|
|
782
554
|
}
|
|
783
555
|
|
|
@@ -856,9 +628,9 @@ class DeviceStrategyFactory {
|
|
|
856
628
|
}
|
|
857
629
|
|
|
858
630
|
class DetectionService {
|
|
859
|
-
logger;
|
|
860
631
|
debug;
|
|
861
632
|
useDocumentClassification;
|
|
633
|
+
useDocumentDetector;
|
|
862
634
|
session;
|
|
863
635
|
mobileNetSession;
|
|
864
636
|
mobileNetClassMap;
|
|
@@ -873,30 +645,32 @@ class DetectionService {
|
|
|
873
645
|
preprocessCanvas;
|
|
874
646
|
preprocessCtx;
|
|
875
647
|
captureCanvas;
|
|
876
|
-
|
|
877
|
-
|
|
648
|
+
// Static canvas pool for reuse across instances
|
|
649
|
+
static canvasPool = new Map();
|
|
650
|
+
static MAX_POOL_SIZE = 5;
|
|
651
|
+
constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
|
|
878
652
|
this.debug = debug;
|
|
879
653
|
this.useDocumentClassification = useDocumentClassification;
|
|
654
|
+
this.useDocumentDetector = useDocumentDetector;
|
|
880
655
|
this.deviceStrategy = DeviceStrategyFactory.createStrategy();
|
|
881
656
|
this.initializeCanvasPool();
|
|
882
657
|
}
|
|
883
658
|
async loadModel() {
|
|
884
659
|
if (this.modelLoaded || this.session) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (!this.useDocumentDetector) {
|
|
663
|
+
// Skip loading the detection model but mark as "loaded" for compatibility
|
|
664
|
+
this.modelLoaded = true;
|
|
889
665
|
return;
|
|
890
666
|
}
|
|
891
667
|
try {
|
|
892
|
-
this.logger.state('PRECARGANDO_MODELO_DETECCION', { modelPath: this.MODEL_PATH });
|
|
893
668
|
const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
|
|
894
669
|
try {
|
|
895
670
|
this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
|
|
896
671
|
}
|
|
897
672
|
catch (error) {
|
|
898
673
|
if (error.message.includes('failed to allocate a buffer')) {
|
|
899
|
-
this.logger.warn('Fallo en asignación de buffer durante precarga, intentando con configuración mínima');
|
|
900
674
|
const fallbackOptions = {
|
|
901
675
|
executionProviders: ['wasm'],
|
|
902
676
|
graphOptimizationLevel: 'disabled',
|
|
@@ -914,10 +688,8 @@ class DetectionService {
|
|
|
914
688
|
}
|
|
915
689
|
}
|
|
916
690
|
this.modelLoaded = true;
|
|
917
|
-
this.logger.state('MODELO_DETECCION_CARGADO_EXITOSAMENTE', { sessionCreated: !!this.session });
|
|
918
691
|
}
|
|
919
692
|
catch (error) {
|
|
920
|
-
this.logger.error('Error al precargar modelo de detección:', error);
|
|
921
693
|
throw error;
|
|
922
694
|
}
|
|
923
695
|
}
|
|
@@ -926,19 +698,14 @@ class DetectionService {
|
|
|
926
698
|
return;
|
|
927
699
|
}
|
|
928
700
|
try {
|
|
929
|
-
this.logger.state('CARGANDO_MODELO_MOBILENET', { path: this.MOBILENET_MODEL_PATH });
|
|
930
701
|
// Try to load class map (optional - SqueezeNet may not need it for basic classification)
|
|
931
702
|
try {
|
|
932
703
|
const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
|
|
933
704
|
if (classResponse.ok) {
|
|
934
705
|
this.mobileNetClassMap = await classResponse.json();
|
|
935
|
-
this.logger.state('CLASES_MOBILENET_CARGADAS', {
|
|
936
|
-
classCount: Object.keys(this.mobileNetClassMap).length
|
|
937
|
-
});
|
|
938
706
|
}
|
|
939
707
|
}
|
|
940
708
|
catch (error) {
|
|
941
|
-
this.logger.warn('No se pudo cargar el mapa de clases de MobileNet, usando clasificación básica');
|
|
942
709
|
// Create a basic class map for document types
|
|
943
710
|
this.mobileNetClassMap = {
|
|
944
711
|
"0": "document",
|
|
@@ -955,7 +722,6 @@ class DetectionService {
|
|
|
955
722
|
}
|
|
956
723
|
catch (error) {
|
|
957
724
|
if (error.message.includes('failed to allocate a buffer')) {
|
|
958
|
-
this.logger.warn('Fallo en asignación de buffer de MobileNet, intentando con configuración mínima');
|
|
959
725
|
const fallbackOptions = {
|
|
960
726
|
executionProviders: ['wasm'],
|
|
961
727
|
graphOptimizationLevel: 'disabled',
|
|
@@ -972,16 +738,16 @@ class DetectionService {
|
|
|
972
738
|
throw error;
|
|
973
739
|
}
|
|
974
740
|
}
|
|
975
|
-
this.logger.state('MODELO_MOBILENET_CARGADO_EXITOSAMENTE', {
|
|
976
|
-
sessionCreated: !!this.mobileNetSession
|
|
977
|
-
});
|
|
978
741
|
}
|
|
979
742
|
catch (error) {
|
|
980
|
-
this.logger.error('Error al cargar modelo MobileNet:', error);
|
|
981
743
|
throw error;
|
|
982
744
|
}
|
|
983
745
|
}
|
|
984
746
|
isModelLoaded() {
|
|
747
|
+
if (!this.useDocumentDetector) {
|
|
748
|
+
// If detector is disabled, consider it "loaded" for compatibility
|
|
749
|
+
return this.modelLoaded;
|
|
750
|
+
}
|
|
985
751
|
return this.modelLoaded && !!this.session;
|
|
986
752
|
}
|
|
987
753
|
preprocess(video) {
|
|
@@ -1006,6 +772,10 @@ class DetectionService {
|
|
|
1006
772
|
return new window.ort.Tensor("float16", float16Data, [1, 3, this.INPUT_SIZE, this.INPUT_SIZE]);
|
|
1007
773
|
}
|
|
1008
774
|
async runInference(inputTensor) {
|
|
775
|
+
if (!this.useDocumentDetector) {
|
|
776
|
+
// Return empty array when document detector is disabled
|
|
777
|
+
return [];
|
|
778
|
+
}
|
|
1009
779
|
if (!this.session) {
|
|
1010
780
|
throw new Error('Detection model not loaded');
|
|
1011
781
|
}
|
|
@@ -1030,11 +800,9 @@ class DetectionService {
|
|
|
1030
800
|
}
|
|
1031
801
|
async classifyDocument(canvas) {
|
|
1032
802
|
if (!this.mobileNetSession || !this.mobileNetClassMap) {
|
|
1033
|
-
this.logger.warn('Modelo MobileNet no está cargado, saltando clasificación');
|
|
1034
803
|
return null;
|
|
1035
804
|
}
|
|
1036
805
|
try {
|
|
1037
|
-
this.logger.state('CLASIFICANDO_DOCUMENTO', { timestamp: Date.now() });
|
|
1038
806
|
const inputTensor = this.preprocessMobileNet(canvas);
|
|
1039
807
|
const feeds = { [this.mobileNetSession.inputNames[0]]: inputTensor };
|
|
1040
808
|
const results = await this.mobileNetSession.run(feeds);
|
|
@@ -1042,13 +810,6 @@ class DetectionService {
|
|
|
1042
810
|
const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
|
|
1043
811
|
const confidence = output[maxIdx];
|
|
1044
812
|
const className = this.mobileNetClassMap[maxIdx.toString()] || "unknown";
|
|
1045
|
-
this.logger.state('DOCUMENTO_CLASIFICADO', {
|
|
1046
|
-
class: className,
|
|
1047
|
-
confidence: confidence.toFixed(3),
|
|
1048
|
-
classIndex: maxIdx,
|
|
1049
|
-
timestamp: Date.now(),
|
|
1050
|
-
results
|
|
1051
|
-
});
|
|
1052
813
|
return {
|
|
1053
814
|
class: className,
|
|
1054
815
|
confidence: confidence,
|
|
@@ -1056,7 +817,6 @@ class DetectionService {
|
|
|
1056
817
|
};
|
|
1057
818
|
}
|
|
1058
819
|
catch (error) {
|
|
1059
|
-
this.logger.error('Error al clasificar documento:', error);
|
|
1060
820
|
return null;
|
|
1061
821
|
}
|
|
1062
822
|
}
|
|
@@ -1169,7 +929,6 @@ class DetectionService {
|
|
|
1169
929
|
}
|
|
1170
930
|
this.mobileNetClassMap = undefined;
|
|
1171
931
|
this.modelLoaded = false;
|
|
1172
|
-
this.logger.state('DETECCION_SERVICE_LIMPIADO', { timestamp: Date.now() });
|
|
1173
932
|
}
|
|
1174
933
|
initializeCanvasPool() {
|
|
1175
934
|
this.preprocessCanvas = document.createElement('canvas');
|
|
@@ -1180,9 +939,6 @@ class DetectionService {
|
|
|
1180
939
|
willReadFrequently: true
|
|
1181
940
|
});
|
|
1182
941
|
this.captureCanvas = document.createElement('canvas');
|
|
1183
|
-
this.logger.state('CANVAS_POOL_INICIALIZADO', {
|
|
1184
|
-
preprocessCanvasSize: this.INPUT_SIZE
|
|
1185
|
-
});
|
|
1186
942
|
}
|
|
1187
943
|
cleanupCanvasPool() {
|
|
1188
944
|
if (this.preprocessCanvas) {
|
|
@@ -1218,10 +974,11 @@ class DetectionService {
|
|
|
1218
974
|
return (sign << 15) | (newExp << 10) | (frac >> 13);
|
|
1219
975
|
}
|
|
1220
976
|
preprocessMobileNet(canvas) {
|
|
1221
|
-
|
|
1222
|
-
tempCanvas
|
|
1223
|
-
tempCanvas.height = 224;
|
|
977
|
+
// Use pooled canvas instead of creating new one
|
|
978
|
+
const tempCanvas = this.getCanvas(224, 224);
|
|
1224
979
|
const tempCtx = tempCanvas.getContext('2d');
|
|
980
|
+
// Clear canvas before use
|
|
981
|
+
tempCtx.clearRect(0, 0, 224, 224);
|
|
1225
982
|
// Resize image to 224x224 for SqueezeNet (using MobileNet interface)
|
|
1226
983
|
tempCtx.drawImage(canvas, 0, 0, 224, 224);
|
|
1227
984
|
const imageData = tempCtx.getImageData(0, 0, 224, 224);
|
|
@@ -1237,8 +994,53 @@ class DetectionService {
|
|
|
1237
994
|
// Blue channel
|
|
1238
995
|
arr[2 * hw + i] = data[i * 4 + 2] / 255.0;
|
|
1239
996
|
}
|
|
997
|
+
// Return canvas to pool after use
|
|
998
|
+
this.returnCanvas(tempCanvas);
|
|
1240
999
|
return new window.ort.Tensor('float32', arr, [1, 3, 224, 224]);
|
|
1241
1000
|
}
|
|
1001
|
+
// Canvas pool management methods for optimization
|
|
1002
|
+
getCanvas(width, height) {
|
|
1003
|
+
const key = `${width}x${height}`;
|
|
1004
|
+
if (!DetectionService.canvasPool.has(key)) {
|
|
1005
|
+
DetectionService.canvasPool.set(key, []);
|
|
1006
|
+
}
|
|
1007
|
+
const pool = DetectionService.canvasPool.get(key);
|
|
1008
|
+
let canvas = pool.pop();
|
|
1009
|
+
if (!canvas) {
|
|
1010
|
+
canvas = document.createElement('canvas');
|
|
1011
|
+
canvas.width = width;
|
|
1012
|
+
canvas.height = height;
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
// Clear the canvas before reuse
|
|
1016
|
+
const ctx = canvas.getContext('2d');
|
|
1017
|
+
ctx.clearRect(0, 0, width, height);
|
|
1018
|
+
}
|
|
1019
|
+
return canvas;
|
|
1020
|
+
}
|
|
1021
|
+
returnCanvas(canvas) {
|
|
1022
|
+
const key = `${canvas.width}x${canvas.height}`;
|
|
1023
|
+
if (!DetectionService.canvasPool.has(key)) {
|
|
1024
|
+
DetectionService.canvasPool.set(key, []);
|
|
1025
|
+
}
|
|
1026
|
+
const pool = DetectionService.canvasPool.get(key);
|
|
1027
|
+
// Only return to pool if not at max capacity
|
|
1028
|
+
if (pool.length < DetectionService.MAX_POOL_SIZE) {
|
|
1029
|
+
pool.push(canvas);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
// Static method to clear all canvas pools (useful for memory management)
|
|
1033
|
+
static clearCanvasPools() {
|
|
1034
|
+
DetectionService.canvasPool.clear();
|
|
1035
|
+
}
|
|
1036
|
+
// Method to get pool statistics for debugging
|
|
1037
|
+
getPoolStats() {
|
|
1038
|
+
const stats = {};
|
|
1039
|
+
DetectionService.canvasPool.forEach((pool, key) => {
|
|
1040
|
+
stats[key] = pool.length;
|
|
1041
|
+
});
|
|
1042
|
+
return stats;
|
|
1043
|
+
}
|
|
1242
1044
|
}
|
|
1243
1045
|
|
|
1244
1046
|
class ServiceContainer {
|
|
@@ -1249,13 +1051,11 @@ class ServiceContainer {
|
|
|
1249
1051
|
initializeServices(config) {
|
|
1250
1052
|
// Initialize services in dependency order
|
|
1251
1053
|
const eventBus = new EventBusService();
|
|
1252
|
-
const logger = new LoggerService(config.debug);
|
|
1253
1054
|
const stateManager = new StateManagerService(eventBus);
|
|
1254
|
-
const cameraService = new CameraService(
|
|
1255
|
-
const detectionService = new DetectionService(
|
|
1055
|
+
const cameraService = new CameraService(eventBus, config.preferredCamera);
|
|
1056
|
+
const detectionService = new DetectionService(config.debug, config.useDocumentClassification, config.useDocumentDetector);
|
|
1256
1057
|
// Register services
|
|
1257
1058
|
this.services.set('eventBus', eventBus);
|
|
1258
|
-
this.services.set('logger', logger);
|
|
1259
1059
|
this.services.set('stateManager', stateManager);
|
|
1260
1060
|
this.services.set('cameraService', cameraService);
|
|
1261
1061
|
this.services.set('detectionService', detectionService);
|
|
@@ -1267,9 +1067,6 @@ class ServiceContainer {
|
|
|
1267
1067
|
}
|
|
1268
1068
|
return service;
|
|
1269
1069
|
}
|
|
1270
|
-
getLogger() {
|
|
1271
|
-
return this.get('logger');
|
|
1272
|
-
}
|
|
1273
1070
|
getEventBus() {
|
|
1274
1071
|
return this.get('eventBus');
|
|
1275
1072
|
}
|
|
@@ -1283,10 +1080,6 @@ class ServiceContainer {
|
|
|
1283
1080
|
return this.get('detectionService');
|
|
1284
1081
|
}
|
|
1285
1082
|
updateConfig(config) {
|
|
1286
|
-
if (config.debug !== undefined) {
|
|
1287
|
-
const logger = this.services.get('logger');
|
|
1288
|
-
logger.setDebugMode(config.debug);
|
|
1289
|
-
}
|
|
1290
1083
|
}
|
|
1291
1084
|
cleanup() {
|
|
1292
1085
|
// Cleanup services in reverse order
|
|
@@ -1296,7 +1089,7 @@ class ServiceContainer {
|
|
|
1296
1089
|
}
|
|
1297
1090
|
}
|
|
1298
1091
|
|
|
1299
|
-
const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);color:#fff;font-size:14px;font-weight:600;text-align:center;white-space:normal;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);padding:12px 20px;border-radius:8px;max-width:300px;z-index:20;border:1px solid rgba(255, 255, 255, 0.1)}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:space-between;align-items:center;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:8px;font-weight:400}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}";
|
|
1092
|
+
const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}}";
|
|
1300
1093
|
|
|
1301
1094
|
const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H {
|
|
1302
1095
|
constructor() {
|
|
@@ -1312,6 +1105,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1312
1105
|
maskSize = 90;
|
|
1313
1106
|
cropMargin = 20;
|
|
1314
1107
|
useDocumentClassification = false;
|
|
1108
|
+
useDocumentDetector = true;
|
|
1315
1109
|
preferredCamera = 'auto';
|
|
1316
1110
|
captureDelay = 1500;
|
|
1317
1111
|
enableBackDocumentTimer = false;
|
|
@@ -1328,9 +1122,16 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1328
1122
|
showCameraSelector = false;
|
|
1329
1123
|
isSwitchingCamera = false;
|
|
1330
1124
|
hasDocumentDetected = false;
|
|
1125
|
+
cameraInfoWithAutofocus = {
|
|
1126
|
+
availableCameras: [],
|
|
1127
|
+
selectedCameraId: null,
|
|
1128
|
+
deviceType: 'desktop',
|
|
1129
|
+
isMultipleCamerasAvailable: false,
|
|
1130
|
+
preferredFacing: null
|
|
1131
|
+
};
|
|
1331
1132
|
currentStatus = {
|
|
1332
|
-
message: '
|
|
1333
|
-
description: 'Configurando
|
|
1133
|
+
message: 'Preparando capturador...',
|
|
1134
|
+
description: 'Configurando herramientas de captura',
|
|
1334
1135
|
type: 'initializing',
|
|
1335
1136
|
isInitialized: false
|
|
1336
1137
|
};
|
|
@@ -1345,9 +1146,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1345
1146
|
detectionRate: 0
|
|
1346
1147
|
};
|
|
1347
1148
|
backDocumentTimerRemaining = 0;
|
|
1149
|
+
showManualCaptureButton = false;
|
|
1348
1150
|
// Services
|
|
1349
1151
|
serviceContainer;
|
|
1350
|
-
logger;
|
|
1351
1152
|
eventBus;
|
|
1352
1153
|
stateManager;
|
|
1353
1154
|
cameraService;
|
|
@@ -1378,19 +1179,25 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1378
1179
|
lastUpdateTime: 0
|
|
1379
1180
|
};
|
|
1380
1181
|
performanceUpdateInterval;
|
|
1381
|
-
// Performance optimization
|
|
1182
|
+
// Performance optimization with adaptive frame skipping
|
|
1382
1183
|
frameSkipCounter = 0;
|
|
1383
|
-
|
|
1184
|
+
BASE_FRAME_SKIP = 2;
|
|
1185
|
+
MAX_FRAME_SKIP = 8;
|
|
1384
1186
|
consecutiveFailures = 0;
|
|
1385
1187
|
MAX_FAILURES = 30;
|
|
1386
1188
|
lastInferenceTime = 0;
|
|
1387
1189
|
MIN_INFERENCE_INTERVAL = 50;
|
|
1190
|
+
performanceHistory = [];
|
|
1191
|
+
PERFORMANCE_HISTORY_SIZE = 10;
|
|
1192
|
+
// Canvas pool for optimized screenshot capture
|
|
1193
|
+
canvasPool = [];
|
|
1194
|
+
MAX_CANVAS_POOL_SIZE = 3;
|
|
1388
1195
|
async componentDidLoad() {
|
|
1389
|
-
this.updateStatus('
|
|
1196
|
+
this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
|
|
1390
1197
|
await this.initializeServices();
|
|
1391
|
-
this.updateStatus('
|
|
1198
|
+
this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
|
|
1392
1199
|
await this.setupEventListeners();
|
|
1393
|
-
this.updateStatus('
|
|
1200
|
+
this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
|
|
1394
1201
|
await this.initializeComponent();
|
|
1395
1202
|
if (this.debug) {
|
|
1396
1203
|
this.initializePerformanceMonitor();
|
|
@@ -1403,37 +1210,28 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1403
1210
|
maskSize: this.maskSize,
|
|
1404
1211
|
cropMargin: this.cropMargin,
|
|
1405
1212
|
useDocumentClassification: this.useDocumentClassification,
|
|
1213
|
+
useDocumentDetector: this.useDocumentDetector,
|
|
1406
1214
|
preferredCamera: this.preferredCamera,
|
|
1407
1215
|
captureDelay: this.captureDelay
|
|
1408
1216
|
};
|
|
1409
1217
|
this.serviceContainer = new ServiceContainer(config);
|
|
1410
|
-
this.logger = this.serviceContainer.getLogger();
|
|
1411
1218
|
this.eventBus = this.serviceContainer.getEventBus();
|
|
1412
1219
|
this.stateManager = this.serviceContainer.getStateManager();
|
|
1413
1220
|
this.cameraService = this.serviceContainer.getCameraService();
|
|
1414
1221
|
this.detectionService = this.serviceContainer.getDetectionService();
|
|
1415
|
-
this.logger.state('SERVICIOS_INICIALIZADOS', { timestamp: Date.now() });
|
|
1416
1222
|
}
|
|
1417
1223
|
async setupEventListeners() {
|
|
1418
1224
|
this.eventBus.on('state-changed', (data) => {
|
|
1419
1225
|
this.handleStateChange(data);
|
|
1420
1226
|
});
|
|
1421
|
-
this.eventBus.on('camera-changed', (
|
|
1422
|
-
|
|
1227
|
+
this.eventBus.on('camera-changed', () => {
|
|
1228
|
+
// Camera changed event
|
|
1423
1229
|
});
|
|
1424
|
-
this.eventBus.on('error', (
|
|
1425
|
-
|
|
1230
|
+
this.eventBus.on('error', () => {
|
|
1231
|
+
// Handle service errors
|
|
1426
1232
|
});
|
|
1427
1233
|
}
|
|
1428
1234
|
async initializeComponent() {
|
|
1429
|
-
this.logger.state('COMPONENTE_INICIALIZANDO', {
|
|
1430
|
-
debug: this.debug,
|
|
1431
|
-
maskSize: this.maskSize,
|
|
1432
|
-
cropMargin: this.cropMargin,
|
|
1433
|
-
useDocumentClassification: this.useDocumentClassification,
|
|
1434
|
-
preferredCamera: this.preferredCamera,
|
|
1435
|
-
captureDelay: this.captureDelay
|
|
1436
|
-
});
|
|
1437
1235
|
this.validateProps();
|
|
1438
1236
|
if (this.debug) {
|
|
1439
1237
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
@@ -1442,58 +1240,67 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1442
1240
|
this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura', 'initializing');
|
|
1443
1241
|
await this.cameraService.detectDeviceType();
|
|
1444
1242
|
await this.cameraService.enumerateDevices();
|
|
1445
|
-
this.
|
|
1243
|
+
await this.updateCameraInfoWithAutofocus();
|
|
1244
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'initializing');
|
|
1446
1245
|
await this.loadOnnxRuntime();
|
|
1447
1246
|
this.initializeResizeObserver();
|
|
1448
1247
|
}
|
|
1449
1248
|
validateProps() {
|
|
1450
1249
|
if (this.maskSize < 50 || this.maskSize > 100) {
|
|
1451
|
-
this.logger.warn(`Propiedad maskSize inválida. Valor: ${this.maskSize}, esperado: 50-100. Usando valor por defecto: 90`);
|
|
1452
1250
|
this.maskSize = 90;
|
|
1453
1251
|
}
|
|
1454
1252
|
if (this.cropMargin < 0 || this.cropMargin > 100) {
|
|
1455
|
-
this.logger.warn(`Propiedad cropMargin inválida. Valor: ${this.cropMargin}, esperado: 0-100. Usando valor por defecto: 0`);
|
|
1456
1253
|
this.cropMargin = 0;
|
|
1457
1254
|
}
|
|
1458
1255
|
const validOptions = ['auto', 'front', 'back'];
|
|
1459
1256
|
if (!validOptions.includes(this.preferredCamera)) {
|
|
1460
|
-
this.logger.warn(`Propiedad preferredCamera inválida. Valor: ${this.preferredCamera}, esperado: ${validOptions.join(', ')}. Usando valor por defecto: 'auto'`);
|
|
1461
1257
|
this.preferredCamera = 'auto';
|
|
1462
1258
|
}
|
|
1463
1259
|
if (this.captureDelay < 0 || this.captureDelay > 10000) {
|
|
1464
|
-
this.logger.warn(`Propiedad captureDelay inválida. Valor: ${this.captureDelay}, esperado: 0-10000. Usando valor por defecto: 1500`);
|
|
1465
1260
|
this.captureDelay = 1500;
|
|
1466
1261
|
}
|
|
1467
1262
|
}
|
|
1468
1263
|
async loadOnnxRuntime() {
|
|
1469
1264
|
if (!window.ort) {
|
|
1470
|
-
this.updateStatus('
|
|
1265
|
+
this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
|
|
1471
1266
|
const script = document.createElement('script');
|
|
1472
1267
|
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
|
|
1473
1268
|
document.head.appendChild(script);
|
|
1474
1269
|
await new Promise((resolve) => {
|
|
1475
1270
|
script.onload = () => {
|
|
1476
|
-
setTimeout(() => {
|
|
1477
|
-
this.finalizeInitialization();
|
|
1271
|
+
setTimeout(async () => {
|
|
1272
|
+
await this.finalizeInitialization();
|
|
1478
1273
|
resolve(undefined);
|
|
1479
1274
|
}, 300);
|
|
1480
1275
|
};
|
|
1481
1276
|
});
|
|
1482
1277
|
}
|
|
1483
1278
|
else {
|
|
1484
|
-
setTimeout(() => {
|
|
1485
|
-
this.finalizeInitialization();
|
|
1279
|
+
setTimeout(async () => {
|
|
1280
|
+
await this.finalizeInitialization();
|
|
1486
1281
|
}, 300);
|
|
1487
1282
|
}
|
|
1488
1283
|
}
|
|
1489
|
-
finalizeInitialization() {
|
|
1284
|
+
async finalizeInitialization() {
|
|
1490
1285
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1286
|
+
// Check camera permissions before showing "ready" status
|
|
1287
|
+
const hasPermissions = await this.checkCameraPermissions();
|
|
1288
|
+
if (hasPermissions) {
|
|
1289
|
+
this.currentStatus = {
|
|
1290
|
+
message: 'Listo para capturar',
|
|
1291
|
+
description: '',
|
|
1292
|
+
type: 'ready',
|
|
1293
|
+
isInitialized: true
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
this.currentStatus = {
|
|
1298
|
+
message: 'Permisos de cámara requeridos',
|
|
1299
|
+
description: 'Por favor, otorgue permisos de cámara para continuar',
|
|
1300
|
+
type: 'error',
|
|
1301
|
+
isInitialized: true
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1497
1304
|
this.emitReadyEvent();
|
|
1498
1305
|
}
|
|
1499
1306
|
updateStatus(message, description, type = 'loading') {
|
|
@@ -1507,11 +1314,26 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1507
1314
|
emitReadyEvent() {
|
|
1508
1315
|
const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
|
|
1509
1316
|
this.isReady.emit(isDocumentReady);
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1317
|
+
}
|
|
1318
|
+
async checkCameraPermissions() {
|
|
1319
|
+
try {
|
|
1320
|
+
if (!navigator.permissions) {
|
|
1321
|
+
// Fallback: try to access camera directly
|
|
1322
|
+
try {
|
|
1323
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
1324
|
+
stream.getTracks().forEach(track => track.stop());
|
|
1325
|
+
return true;
|
|
1326
|
+
}
|
|
1327
|
+
catch {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
1332
|
+
return permission.state === 'granted';
|
|
1333
|
+
}
|
|
1334
|
+
catch (error) {
|
|
1335
|
+
return false;
|
|
1336
|
+
}
|
|
1515
1337
|
}
|
|
1516
1338
|
handleStateChange(data) {
|
|
1517
1339
|
}
|
|
@@ -1528,9 +1350,31 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1528
1350
|
const container = this.detectionContainer.parentElement;
|
|
1529
1351
|
const rect = container.getBoundingClientRect();
|
|
1530
1352
|
this.updateMaskDimensions(rect);
|
|
1531
|
-
this.logger.debug('Container redimensionado:', { width: rect.width, height: rect.height });
|
|
1532
1353
|
}
|
|
1533
1354
|
}
|
|
1355
|
+
getGuideText(step) {
|
|
1356
|
+
if (step === 'completed') {
|
|
1357
|
+
return 'Proceso completado';
|
|
1358
|
+
}
|
|
1359
|
+
if (step === 'front') {
|
|
1360
|
+
// Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
|
|
1361
|
+
if (this.showManualCaptureButton) {
|
|
1362
|
+
return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
|
|
1363
|
+
}
|
|
1364
|
+
// Instrucción básica de alineación
|
|
1365
|
+
return 'Alinee su identificación con el marco';
|
|
1366
|
+
}
|
|
1367
|
+
else if (step === 'back') {
|
|
1368
|
+
// Para el reverso, mostrar instrucciones específicas según el modo
|
|
1369
|
+
if (this.useDocumentDetector) {
|
|
1370
|
+
return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
return 'Alinee su identificación con el marco';
|
|
1377
|
+
}
|
|
1534
1378
|
updateMaskDimensions(containerRect) {
|
|
1535
1379
|
if (!this.videoRef)
|
|
1536
1380
|
return;
|
|
@@ -1582,16 +1426,38 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1582
1426
|
this.el.style.setProperty('--mask-center-x', `${videoCenterXPercent}%`);
|
|
1583
1427
|
this.el.style.setProperty('--mask-center-y', `${videoCenterYPercent}%`);
|
|
1584
1428
|
this.isMaskReady = true;
|
|
1585
|
-
this.logger.state('DIMENSIONES_MASCARA_ACTUALIZADAS', {
|
|
1586
|
-
video: { width: videoWidth, height: videoHeight },
|
|
1587
|
-
displayed: { width: displayedVideoWidth, height: displayedVideoHeight },
|
|
1588
|
-
mask: { widthPercent: maskWidthPercent, heightPercent: maskHeightPercent },
|
|
1589
|
-
center: { x: videoCenterXPercent, y: videoCenterYPercent },
|
|
1590
|
-
offset: { x: videoOffsetX, y: videoOffsetY }
|
|
1591
|
-
});
|
|
1592
1429
|
}
|
|
1593
1430
|
// PUBLIC METHODS
|
|
1431
|
+
// Helper method to check if component is ready for operations
|
|
1432
|
+
isComponentReady() {
|
|
1433
|
+
if (!this.currentStatus.isInitialized) {
|
|
1434
|
+
return {
|
|
1435
|
+
ready: false,
|
|
1436
|
+
message: 'El componente aún se está inicializando',
|
|
1437
|
+
status: 'initializing'
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
if (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') {
|
|
1441
|
+
return {
|
|
1442
|
+
ready: false,
|
|
1443
|
+
message: 'El componente está cargando recursos',
|
|
1444
|
+
status: 'loading'
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
if (!this.serviceContainer || !this.stateManager || !this.cameraService || !this.detectionService) {
|
|
1448
|
+
return {
|
|
1449
|
+
ready: false,
|
|
1450
|
+
message: 'Los servicios del componente no están disponibles',
|
|
1451
|
+
status: 'error'
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
return { ready: true };
|
|
1455
|
+
}
|
|
1594
1456
|
async getCapturedImages() {
|
|
1457
|
+
const readyCheck = this.isComponentReady();
|
|
1458
|
+
if (!readyCheck.ready) {
|
|
1459
|
+
throw new Error(readyCheck.message);
|
|
1460
|
+
}
|
|
1595
1461
|
const state = this.stateManager.getCaptureState();
|
|
1596
1462
|
if (state.step !== 'completed') {
|
|
1597
1463
|
throw new Error('El proceso de captura no ha sido completado');
|
|
@@ -1599,47 +1465,128 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1599
1465
|
return this.stateManager.getCapturedImages();
|
|
1600
1466
|
}
|
|
1601
1467
|
async isProcessCompleted() {
|
|
1468
|
+
const readyCheck = this.isComponentReady();
|
|
1469
|
+
if (!readyCheck.ready) {
|
|
1470
|
+
return false; // Return false instead of error for this method
|
|
1471
|
+
}
|
|
1602
1472
|
return this.stateManager.isProcessCompleted();
|
|
1603
1473
|
}
|
|
1604
1474
|
async startCapture() {
|
|
1605
|
-
|
|
1475
|
+
const readyCheck = this.isComponentReady();
|
|
1476
|
+
if (!readyCheck.ready) {
|
|
1477
|
+
this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
|
|
1478
|
+
return { success: false, error: readyCheck.message };
|
|
1479
|
+
}
|
|
1480
|
+
try {
|
|
1481
|
+
await this.startDetection();
|
|
1482
|
+
return { success: true };
|
|
1483
|
+
}
|
|
1484
|
+
catch (error) {
|
|
1485
|
+
this.updateStatus('Error al iniciar captura', error.message, 'error');
|
|
1486
|
+
return { success: false, error: error.message };
|
|
1487
|
+
}
|
|
1606
1488
|
}
|
|
1607
1489
|
async stopCapture() {
|
|
1608
|
-
this.
|
|
1490
|
+
const readyCheck = this.isComponentReady();
|
|
1491
|
+
if (!readyCheck.ready) {
|
|
1492
|
+
return { success: false, error: readyCheck.message };
|
|
1493
|
+
}
|
|
1494
|
+
try {
|
|
1495
|
+
this.exitSession();
|
|
1496
|
+
return { success: true };
|
|
1497
|
+
}
|
|
1498
|
+
catch (error) {
|
|
1499
|
+
return { success: false, error: error.message };
|
|
1500
|
+
}
|
|
1609
1501
|
}
|
|
1610
1502
|
async resetCapture() {
|
|
1611
|
-
this.
|
|
1503
|
+
const readyCheck = this.isComponentReady();
|
|
1504
|
+
if (!readyCheck.ready) {
|
|
1505
|
+
return { success: false, error: readyCheck.message };
|
|
1506
|
+
}
|
|
1507
|
+
try {
|
|
1508
|
+
this.resetDetection();
|
|
1509
|
+
return { success: true };
|
|
1510
|
+
}
|
|
1511
|
+
catch (error) {
|
|
1512
|
+
return { success: false, error: error.message };
|
|
1513
|
+
}
|
|
1612
1514
|
}
|
|
1613
1515
|
async skipBackCapture() {
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1516
|
+
const readyCheck = this.isComponentReady();
|
|
1517
|
+
if (!readyCheck.ready) {
|
|
1518
|
+
return { success: false, error: readyCheck.message };
|
|
1519
|
+
}
|
|
1520
|
+
try {
|
|
1521
|
+
const captureState = this.stateManager.getCaptureState();
|
|
1522
|
+
const capturedImages = this.stateManager.getCapturedImages();
|
|
1523
|
+
if (captureState.step === 'back' && capturedImages.front.fullFrame) {
|
|
1524
|
+
this.completeProcess(true);
|
|
1525
|
+
return { success: true };
|
|
1526
|
+
}
|
|
1527
|
+
else {
|
|
1528
|
+
return { success: false, error: 'No se puede saltar el reverso en el estado actual' };
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
return { success: false, error: error.message };
|
|
1618
1533
|
}
|
|
1619
1534
|
}
|
|
1620
1535
|
async getStatus() {
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1536
|
+
const readyCheck = this.isComponentReady();
|
|
1537
|
+
if (!readyCheck.ready) {
|
|
1538
|
+
return {
|
|
1539
|
+
isVideoActive: false,
|
|
1540
|
+
captureStep: 'front',
|
|
1541
|
+
hasImages: false,
|
|
1542
|
+
isProcessCompleted: false,
|
|
1543
|
+
isModelPreloaded: false,
|
|
1544
|
+
componentStatus: readyCheck.status,
|
|
1545
|
+
componentMessage: readyCheck.message
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
try {
|
|
1549
|
+
const captureState = this.stateManager.getCaptureState();
|
|
1550
|
+
const capturedImages = this.stateManager.getCapturedImages();
|
|
1551
|
+
return {
|
|
1552
|
+
isVideoActive: captureState.isVideoActive,
|
|
1553
|
+
captureStep: captureState.step,
|
|
1554
|
+
hasImages: !!(capturedImages.front.fullFrame || capturedImages.back.fullFrame),
|
|
1555
|
+
isProcessCompleted: this.stateManager.isProcessCompleted(),
|
|
1556
|
+
isModelPreloaded: this.detectionService.isModelLoaded(),
|
|
1557
|
+
componentStatus: 'ready'
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
catch (error) {
|
|
1561
|
+
return {
|
|
1562
|
+
isVideoActive: false,
|
|
1563
|
+
captureStep: 'front',
|
|
1564
|
+
hasImages: false,
|
|
1565
|
+
isProcessCompleted: false,
|
|
1566
|
+
isModelPreloaded: false,
|
|
1567
|
+
componentStatus: 'error',
|
|
1568
|
+
componentMessage: error.message
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1630
1571
|
}
|
|
1631
1572
|
async preloadModel() {
|
|
1573
|
+
const readyCheck = this.isComponentReady();
|
|
1574
|
+
if (!readyCheck.ready) {
|
|
1575
|
+
this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
|
|
1576
|
+
return { success: false, error: readyCheck.message };
|
|
1577
|
+
}
|
|
1632
1578
|
if (this.detectionService.isModelLoaded()) {
|
|
1633
|
-
this.
|
|
1634
|
-
this.updateStatus('Modelos ya cargados', '', 'ready');
|
|
1579
|
+
this.updateStatus('Reconocimiento listo', '', 'ready');
|
|
1635
1580
|
return { success: true, message: 'Model already loaded' };
|
|
1636
1581
|
}
|
|
1637
1582
|
try {
|
|
1638
1583
|
const loadStartTime = performance.now();
|
|
1639
|
-
this.updateStatus('
|
|
1640
|
-
this.stateManager
|
|
1584
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
|
|
1585
|
+
if (this.stateManager) {
|
|
1586
|
+
this.stateManager.updateCaptureState({ isLoading: true });
|
|
1587
|
+
}
|
|
1641
1588
|
await this.detectionService.loadModel();
|
|
1642
|
-
this.updateStatus('
|
|
1589
|
+
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
1643
1590
|
await this.detectionService.loadClassificationModel();
|
|
1644
1591
|
const loadEndTime = performance.now();
|
|
1645
1592
|
const loadTime = loadEndTime - loadStartTime;
|
|
@@ -1647,80 +1594,118 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1647
1594
|
if (this.debug) {
|
|
1648
1595
|
this.recordOnnxPerformance(loadTime, 0);
|
|
1649
1596
|
}
|
|
1650
|
-
this.updateStatus('
|
|
1597
|
+
this.updateStatus('Finalizando configuración...', 'Preparando herramientas para captura', 'loading');
|
|
1651
1598
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
1652
|
-
this.updateStatus('
|
|
1653
|
-
this.stateManager
|
|
1599
|
+
this.updateStatus('Reconocimiento preparado', '', 'ready');
|
|
1600
|
+
if (this.stateManager) {
|
|
1601
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1602
|
+
}
|
|
1654
1603
|
this.emitReadyEvent();
|
|
1655
|
-
this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', { loadTime: Math.round(loadTime) });
|
|
1656
1604
|
return { success: true, message: 'Models preloaded successfully' };
|
|
1657
1605
|
}
|
|
1658
1606
|
catch (error) {
|
|
1659
|
-
this.
|
|
1660
|
-
this.
|
|
1661
|
-
|
|
1607
|
+
this.updateStatus('Error de configuración', 'No se pudieron preparar las herramientas necesarias', 'error');
|
|
1608
|
+
if (this.stateManager) {
|
|
1609
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1610
|
+
}
|
|
1662
1611
|
return { success: false, error: error.message };
|
|
1663
1612
|
}
|
|
1664
1613
|
}
|
|
1665
1614
|
async getCameraInfo() {
|
|
1666
|
-
|
|
1615
|
+
const readyCheck = this.isComponentReady();
|
|
1616
|
+
if (!readyCheck.ready) {
|
|
1617
|
+
return {
|
|
1618
|
+
availableCameras: [],
|
|
1619
|
+
selectedCameraId: null,
|
|
1620
|
+
deviceType: 'desktop',
|
|
1621
|
+
isMultipleCamerasAvailable: false,
|
|
1622
|
+
preferredFacing: null,
|
|
1623
|
+
error: readyCheck.message
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
try {
|
|
1627
|
+
return await this.cameraService.getCameraInfo();
|
|
1628
|
+
}
|
|
1629
|
+
catch (error) {
|
|
1630
|
+
return {
|
|
1631
|
+
availableCameras: [],
|
|
1632
|
+
selectedCameraId: null,
|
|
1633
|
+
deviceType: 'desktop',
|
|
1634
|
+
isMultipleCamerasAvailable: false,
|
|
1635
|
+
preferredFacing: null,
|
|
1636
|
+
error: error.message
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1667
1639
|
}
|
|
1668
1640
|
async setPreferredCamera(camera) {
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1641
|
+
const readyCheck = this.isComponentReady();
|
|
1642
|
+
if (!readyCheck.ready) {
|
|
1643
|
+
return {
|
|
1644
|
+
success: false,
|
|
1645
|
+
error: readyCheck.message,
|
|
1646
|
+
selectedCamera: null,
|
|
1647
|
+
availableCameras: 0
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
try {
|
|
1651
|
+
this.preferredCamera = camera;
|
|
1652
|
+
this.serviceContainer.updateConfig({ preferredCamera: camera });
|
|
1653
|
+
await this.cameraService.enumerateDevices();
|
|
1654
|
+
await this.updateCameraInfoWithAutofocus();
|
|
1655
|
+
const captureState = this.stateManager.getCaptureState();
|
|
1656
|
+
if (captureState.isVideoActive) {
|
|
1657
|
+
const selectedCameraId = this.cameraService.getSelectedCameraId();
|
|
1658
|
+
if (selectedCameraId) {
|
|
1659
|
+
await this.cameraService.switchCamera(selectedCameraId);
|
|
1660
|
+
}
|
|
1677
1661
|
}
|
|
1662
|
+
return {
|
|
1663
|
+
success: true,
|
|
1664
|
+
selectedCamera: this.cameraService.getSelectedCameraId(),
|
|
1665
|
+
availableCameras: this.cameraService.getAvailableCameras().length
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
catch (error) {
|
|
1669
|
+
return {
|
|
1670
|
+
success: false,
|
|
1671
|
+
error: error.message,
|
|
1672
|
+
selectedCamera: null,
|
|
1673
|
+
availableCameras: 0
|
|
1674
|
+
};
|
|
1678
1675
|
}
|
|
1679
|
-
return {
|
|
1680
|
-
success: true,
|
|
1681
|
-
selectedCamera: this.cameraService.getSelectedCameraId(),
|
|
1682
|
-
availableCameras: this.cameraService.getAvailableCameras().length
|
|
1683
|
-
};
|
|
1684
1676
|
}
|
|
1685
1677
|
async setCaptureDelay(delay) {
|
|
1686
1678
|
if (delay < 0 || delay > 10000) {
|
|
1687
|
-
|
|
1679
|
+
return {
|
|
1680
|
+
success: false,
|
|
1681
|
+
error: 'Capture delay must be between 0 and 10000 milliseconds',
|
|
1682
|
+
captureDelay: this.captureDelay
|
|
1683
|
+
};
|
|
1688
1684
|
}
|
|
1685
|
+
// Allow setting capture delay even during initialization
|
|
1689
1686
|
this.captureDelay = delay;
|
|
1690
|
-
this.serviceContainer
|
|
1687
|
+
if (this.serviceContainer) {
|
|
1688
|
+
this.serviceContainer.updateConfig({ captureDelay: delay });
|
|
1689
|
+
}
|
|
1691
1690
|
return {
|
|
1692
1691
|
success: true,
|
|
1693
1692
|
captureDelay: this.captureDelay
|
|
1694
1693
|
};
|
|
1695
1694
|
}
|
|
1696
1695
|
async getCaptureDelay() {
|
|
1696
|
+
// Always allow getting capture delay, even during initialization
|
|
1697
1697
|
return this.captureDelay;
|
|
1698
1698
|
}
|
|
1699
|
-
async setTorchEnabled(enabled) {
|
|
1700
|
-
const torchEnabled = await this.cameraService.setTorchEnabled(enabled, this.videoStream);
|
|
1701
|
-
return {
|
|
1702
|
-
success: torchEnabled,
|
|
1703
|
-
enabled: torchEnabled ? enabled : false
|
|
1704
|
-
};
|
|
1705
|
-
}
|
|
1706
|
-
async focusAtPoint(x, y) {
|
|
1707
|
-
const focused = await this.cameraService.focusAtPoint(x, y, this.videoStream);
|
|
1708
|
-
return {
|
|
1709
|
-
success: focused,
|
|
1710
|
-
coordinates: { x, y }
|
|
1711
|
-
};
|
|
1712
|
-
}
|
|
1713
1699
|
// DETECTION METHODS
|
|
1714
1700
|
async startDetection() {
|
|
1715
|
-
this.logger.state('INICIANDO_DETECCION');
|
|
1716
1701
|
try {
|
|
1717
1702
|
// Paso 1: Verificar y cargar modelos si es necesario
|
|
1718
1703
|
if (!this.detectionService.isModelLoaded()) {
|
|
1719
1704
|
const loadStartTime = performance.now();
|
|
1720
|
-
this.updateStatus('
|
|
1705
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
|
|
1721
1706
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
1722
1707
|
await this.detectionService.loadModel();
|
|
1723
|
-
this.updateStatus('
|
|
1708
|
+
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
1724
1709
|
await this.detectionService.loadClassificationModel();
|
|
1725
1710
|
const loadEndTime = performance.now();
|
|
1726
1711
|
const loadTime = loadEndTime - loadStartTime;
|
|
@@ -1728,16 +1713,21 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1728
1713
|
if (this.debug) {
|
|
1729
1714
|
this.recordOnnxPerformance(loadTime, 0);
|
|
1730
1715
|
}
|
|
1731
|
-
this.logger.state('MODELOS_CARGADOS_EN_DETECCION', { loadTime: Math.round(loadTime) });
|
|
1732
1716
|
}
|
|
1733
1717
|
// Paso 2: Detectar y configurar dispositivos
|
|
1734
|
-
this.updateStatus('
|
|
1718
|
+
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
1735
1719
|
await this.cameraService.enumerateDevices();
|
|
1720
|
+
await this.updateCameraInfoWithAutofocus();
|
|
1736
1721
|
// Paso 3: Configurar cámara seleccionada
|
|
1737
|
-
this.updateStatus('
|
|
1722
|
+
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
1738
1723
|
const stream = await this.cameraService.setupCamera();
|
|
1739
1724
|
// Paso 4: Inicializar video y ocultar estado inmediatamente
|
|
1740
|
-
this.
|
|
1725
|
+
if (this.useDocumentDetector) {
|
|
1726
|
+
this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
|
|
1727
|
+
}
|
|
1728
|
+
else {
|
|
1729
|
+
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
1730
|
+
}
|
|
1741
1731
|
await this.initializeVideoStream(stream);
|
|
1742
1732
|
// Paso 5: Calibrar máscara
|
|
1743
1733
|
if (this.detectionContainer) {
|
|
@@ -1748,11 +1738,14 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1748
1738
|
// Finalizar e iniciar captura
|
|
1749
1739
|
this.startTime = Date.now();
|
|
1750
1740
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1741
|
+
// Mostrar botón manual si el detector está deshabilitado
|
|
1742
|
+
if (!this.useDocumentDetector) {
|
|
1743
|
+
this.showManualCaptureButton = true;
|
|
1744
|
+
}
|
|
1751
1745
|
this.detectFrame();
|
|
1752
1746
|
}
|
|
1753
1747
|
catch (err) {
|
|
1754
|
-
this.
|
|
1755
|
-
this.updateStatus('Error al iniciar captura', 'No se pudo completar la inicialización', 'error');
|
|
1748
|
+
this.updateStatus('Error al iniciar', 'No se pudo preparar la captura', 'error');
|
|
1756
1749
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1757
1750
|
}
|
|
1758
1751
|
}
|
|
@@ -1762,10 +1755,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1762
1755
|
this.videoStream = stream;
|
|
1763
1756
|
const isRear = this.cameraService.isRearCamera(stream);
|
|
1764
1757
|
this.shouldMirrorVideo = !isRear;
|
|
1765
|
-
this.logger.state('CAMARA_CONFIGURADA', {
|
|
1766
|
-
isRearCamera: isRear,
|
|
1767
|
-
shouldMirrorVideo: this.shouldMirrorVideo
|
|
1768
|
-
});
|
|
1769
1758
|
return new Promise((resolve) => {
|
|
1770
1759
|
this.videoRef.onloadedmetadata = async () => {
|
|
1771
1760
|
await this.videoRef.play();
|
|
@@ -1784,15 +1773,28 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1784
1773
|
const captureState = this.stateManager.getCaptureState();
|
|
1785
1774
|
if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
|
|
1786
1775
|
return;
|
|
1776
|
+
// Show manual capture button when document detector is disabled
|
|
1777
|
+
if (!this.useDocumentDetector) {
|
|
1778
|
+
this.showManualCaptureButton = true;
|
|
1779
|
+
// Continue the loop for UI updates but skip detection logic
|
|
1780
|
+
if (captureState.step !== 'completed') {
|
|
1781
|
+
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
1782
|
+
}
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
else {
|
|
1786
|
+
this.showManualCaptureButton = false;
|
|
1787
|
+
}
|
|
1787
1788
|
if (captureState.isDetectionPaused) {
|
|
1788
1789
|
if (captureState.step !== 'completed') {
|
|
1789
1790
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
1790
1791
|
}
|
|
1791
1792
|
return;
|
|
1792
1793
|
}
|
|
1793
|
-
//
|
|
1794
|
+
// Adaptive frame skipping for performance
|
|
1794
1795
|
this.frameSkipCounter++;
|
|
1795
|
-
|
|
1796
|
+
const currentFrameSkip = this.getAdaptiveFrameSkip();
|
|
1797
|
+
if (this.frameSkipCounter <= currentFrameSkip) {
|
|
1796
1798
|
if (captureState.step !== 'completed') {
|
|
1797
1799
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
1798
1800
|
}
|
|
@@ -1868,7 +1870,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1868
1870
|
}
|
|
1869
1871
|
}
|
|
1870
1872
|
catch (e) {
|
|
1871
|
-
this.logger.error('Error en inferencia de modelo:', e);
|
|
1872
1873
|
const captureState = this.stateManager.getCaptureState();
|
|
1873
1874
|
if (captureState.step !== 'completed') {
|
|
1874
1875
|
setTimeout(() => this.detectFrame(), 100);
|
|
@@ -1895,6 +1896,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1895
1896
|
clearInterval(this.performanceUpdateInterval);
|
|
1896
1897
|
this.performanceUpdateInterval = undefined;
|
|
1897
1898
|
}
|
|
1899
|
+
// Clear canvas pool
|
|
1900
|
+
this.canvasPool = [];
|
|
1898
1901
|
this.detectionBoxes = [];
|
|
1899
1902
|
this.alignmentStartTime = undefined;
|
|
1900
1903
|
this.hasDocumentDetected = false;
|
|
@@ -1908,11 +1911,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1908
1911
|
step: 'front',
|
|
1909
1912
|
isCapturing: false
|
|
1910
1913
|
};
|
|
1911
|
-
const cameraInfo = this.
|
|
1912
|
-
|
|
1913
|
-
selectedCameraId: null,
|
|
1914
|
-
deviceType: 'desktop'};
|
|
1915
|
-
return (h("div", { key: '9d4d77042218ab3d0bc4fb1ce879db09dc3e78b4', class: "detector-container" }, h("div", { key: '51ac8ccb1275637282b038ad8364d19f14ef61b1', class: "video-container" }, h("video", { key: 'a34f0481da20ec5b7b62b21aa275ec5f6dfec5d1', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '43b71dfe3eaf20ffa66a4139e24b1af6a2074631', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
|
|
1914
|
+
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
1915
|
+
return (h("div", { key: 'e0b32b1b41b58e3a58b552199384080e8f2c069b', class: "detector-container" }, h("div", { key: '03c77af311ff12d936994d629d58550cbaae190c', class: "video-container" }, h("video", { key: '94f15b34f34367a10d17816a31cd147adb57bc90', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '982be192792dfaf644eb173bfee9b26ebd229288', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
|
|
1916
1916
|
position: 'absolute',
|
|
1917
1917
|
left: `${box.x}px`,
|
|
1918
1918
|
top: `${box.y}px`,
|
|
@@ -1921,9 +1921,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
1921
1921
|
border: '2px solid #32406C',
|
|
1922
1922
|
pointerEvents: 'none',
|
|
1923
1923
|
boxSizing: 'border-box'
|
|
1924
|
-
} })))), this.isMaskReady && (h("div", { key: '
|
|
1924
|
+
} })))), this.isMaskReady && (h("div", { key: 'f5212ff70db65d2b836a3ac456a1701701630660', class: "overlay-mask" }, h("div", { key: '5080217f4962c2c7152def39cc978ba1c40a3a1c', class: "card-outline" }, h("div", { key: 'd37f1ca8723d79b10d1c6a3afaf54e2f2657ae79', class: "side side-top" }), h("div", { key: 'c5bb4f7d646a991f43598688ffa909c04697bb06', class: "side side-right" }), h("div", { key: '4a5040b4a236367dcdf72f4e72d497b8b6dee6c0', class: "side side-bottom" }), h("div", { key: 'c74ecef56eee8e92cd9491d693fa89a6c01bdbbd', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '2a2932b0c03835193ebf86ddb5f13173b1dba367', class: "guide-text" }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '41fcf2d79106d3ed928fc2842cb6a6725ba4d4f6', class: "back-capture-section" }, h("div", { key: '98dbe1bbe454bad42c3e155247f25a9151aaf1f9', class: "back-capture-buttons" }, !this.useDocumentDetector && (h("button", { key: '01693bc0d6915a4d02f318268020dfbcf3a70b94', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), h("button", { key: '0a4ba55cbbea867b936a83a16a622a3c36a225f9', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
1925
1925
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
1926
|
-
: 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: '
|
|
1926
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: '1db208a53dea37211bdd88254e575142b7abf91f', class: "camera-controls" }, h("button", { key: '90755839358f997c3ce2f4c36a8d17d39ea5f8ad', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: 'ef7889ab82f9eaabcabec3e3dc3ebb7850818dd4', class: "camera-selector-dropdown" }, h("div", { key: 'e28fb005316a742318be3cf8582f520392b71bba', class: "camera-selector-header" }, h("span", { key: '97338030b1aedb50c5f5a588ea03c0f19bcb0ddd' }, "Seleccionar C\u00E1mara"), h("button", { key: '1537c907dfab17c232a256f9fc9210cbd0125cc6', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '509178119e6eed5fe0a6ea287904a53bd9fbecbc', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '485f7e1a97c43ed280a106bde3c6ee910625025c', class: "device-info" }, h("small", { key: 'd76edba943f4e06c14024923496555fa1fe6d845' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'c5184dd93aa41cff61b88ea2046b481efc87c1f2', class: "manual-capture-section" }, h("button", { key: '6e45d9a0fec60469eb926ff353cb1a440d59f5fe', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (h("div", { key: '9e296a942ba51cc1fd152e76ae406620c84ed721', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '517db50f7273788d94d5e6db6c40f65e00e18784', class: "flip-animation" }, h("div", { key: 'fbf6a26c4cc0539bd16b45899d19984af3449610', class: "id-card-icon" }), h("div", { key: 'fb3a201864d04531a078074458aa6e7f6580d565', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '10c694e233d26523d538ff0d2edbf6dcfb8e37c8', class: "success-animation" }, h("div", { key: '87e545650825ab805e887667281ecdf8bdb34c45', class: "check-icon" }), h("div", { key: '359da71f391d68e9437b749b949ee90685b96b3f', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '7c5533bb875eb3d9cdcc6cfdfc20e4d5bd98d103', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'f32df46ab83279387de606eadf53cd4a4009f449', class: "status-spinner" })), h("div", { key: 'dd640de5a3b64f5f99363318e2fc70e32ccbc7db', class: "status-content" }, h("div", { key: '618c0ab9cb0a24b0af3f6946052beab0d660fe6d', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'e52c2a4945c7f5a190a446ac588aa1668c21f26d', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '61cea10bc3438fb74aa176d71c4238b070cb0f09', class: "performance-monitor" }, h("div", { key: '44f6b1accb56d99a236b22c0769f870638d9c768', class: "performance-expanded" }, h("div", { key: '6febc85e319752b0aebc0a4e4050ea6d597375a7', class: "metrics-row" }, h("div", { key: '96b81895ad3cd742c96308ed53a2126af76eb619', class: "metric-compact" }, h("span", { key: '774a11c6fed6a87db7673b018b3cdc378c5252ef', class: "metric-label" }, "FPS"), h("span", { key: 'cb866dcbea661e5f715b82c4dd760f786fd5bb2a', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'a935aaadb83ea400bfaa984d1d845bdfd87becc8', class: "metric-compact" }, h("span", { key: 'ffacaa2cb7838403a297145b70586e255cc845c7', class: "metric-label" }, "MEM"), h("span", { key: 'a1d7a7abec97dc0544d50b11b372f699f750b76f', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'bb3d156a46569a8c290bc518c42ec7957bc5b94e', class: "metrics-row" }, h("div", { key: '4cadd8f845e4c12120ed549e4a73e02c6cbd9257', class: "metric-compact" }, h("span", { key: '1fc5661222f495b79fd7eb126e04f52eecab6fb3', class: "metric-label" }, "INF"), h("span", { key: 'eca1f59d1c41ab99158eec81592bc7150bfeabe1', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'f708e2757adcb03e633111d96bd6786fb6b80ea6', class: "metric-compact" }, h("span", { key: 'a13099ded64180996d6f65bf215483ba83daccbc', class: "metric-label" }, "FRAME"), h("span", { key: '2765cd1f7c1a49df9de64639cc1b777c79f89f4c', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '13aaf85a1b4b66294c4204f02d956b64665aa766', class: "metrics-row" }, h("div", { key: '40012f3f646d70c535a606ebbe581a2fe6fd2e50', class: "metric-compact" }, h("span", { key: '03f8b17746fe404485820d06a50173678db2fd25', class: "metric-label" }, "DET"), h("span", { key: 'c4f87430f142355054d9361330d6a5d508974b70', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'cf366a41d7f7cda6046c32cc7c514eb7b46b05d1', class: "metric-compact" }, h("span", { key: '925e7de15b96ba4dbbcdbf8a9f0e3173bae49573', class: "metric-label" }, "RATE"), h("span", { key: '9701e60c7793f2c205f3c9ffd254b6a115b8a68c', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '636aeb9b2b8dd992d8a28a8db44b213e12514096', class: "watermark" }, h("img", { key: '0407b550d962a6113e05ad75e25a1a1bbd76d9ce', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
1927
1927
|
}
|
|
1928
1928
|
// Utility methods
|
|
1929
1929
|
updateDetectionBoxes(boxes) {
|
|
@@ -2004,35 +2004,18 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2004
2004
|
if (allSidesAligned && bestBox) {
|
|
2005
2005
|
cardOutline?.classList.add('perfect-match');
|
|
2006
2006
|
corners?.forEach(corner => corner.classList.add('perfect-match'));
|
|
2007
|
-
// Debug logging
|
|
2008
|
-
this.logger.state('CAPTURE_EVALUATION', {
|
|
2009
|
-
allSidesAligned: true,
|
|
2010
|
-
hasScreenshotTaken: this.hasScreenshotTaken,
|
|
2011
|
-
captureDelay: this.captureDelay,
|
|
2012
|
-
alignmentStartTime: this.alignmentStartTime
|
|
2013
|
-
});
|
|
2014
2007
|
if (!this.hasScreenshotTaken) {
|
|
2015
2008
|
const currentTime = Date.now();
|
|
2016
2009
|
// Initialize alignment start time if not set
|
|
2017
2010
|
if (!this.alignmentStartTime) {
|
|
2018
2011
|
this.alignmentStartTime = currentTime;
|
|
2019
|
-
this.logger.state('ALIGNMENT_TIMER_STARTED', { startTime: currentTime });
|
|
2020
2012
|
}
|
|
2021
2013
|
// Check if document has been aligned for the configured delay
|
|
2022
2014
|
const alignmentDuration = currentTime - this.alignmentStartTime;
|
|
2023
|
-
this.logger.state('ALIGNMENT_DURATION_CHECK', {
|
|
2024
|
-
alignmentDuration,
|
|
2025
|
-
captureDelay: this.captureDelay,
|
|
2026
|
-
readyToCapture: alignmentDuration >= this.captureDelay
|
|
2027
|
-
});
|
|
2028
2015
|
if (alignmentDuration >= this.captureDelay) {
|
|
2029
|
-
this.logger.state('TRIGGERING_CAPTURE', {
|
|
2030
|
-
alignmentDuration,
|
|
2031
|
-
captureDelay: this.captureDelay
|
|
2032
|
-
});
|
|
2033
2016
|
this.lastDetectedBox = bestBox;
|
|
2034
|
-
this.takeScreenshot().catch(
|
|
2035
|
-
|
|
2017
|
+
this.takeScreenshot().catch(() => {
|
|
2018
|
+
// Handle screenshot error silently
|
|
2036
2019
|
});
|
|
2037
2020
|
this.hasScreenshotTaken = true;
|
|
2038
2021
|
this.alignmentStartTime = undefined;
|
|
@@ -2055,24 +2038,122 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2055
2038
|
}
|
|
2056
2039
|
}
|
|
2057
2040
|
}
|
|
2041
|
+
async takeManualScreenshot() {
|
|
2042
|
+
if (!this.videoRef)
|
|
2043
|
+
return;
|
|
2044
|
+
// When using manual capture, use mask coordinates for cropping
|
|
2045
|
+
await this.takeScreenshotWithMaskCoordinates();
|
|
2046
|
+
}
|
|
2047
|
+
async takeScreenshotWithMaskCoordinates() {
|
|
2048
|
+
if (!this.videoRef || !this.detectionContainer)
|
|
2049
|
+
return;
|
|
2050
|
+
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
2051
|
+
this.triggerCaptureAnimation();
|
|
2052
|
+
// Use pooled canvas for optimization
|
|
2053
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
2054
|
+
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
2055
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
2056
|
+
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
2057
|
+
// Calculate mask coordinates for cropping
|
|
2058
|
+
const container = this.detectionContainer.parentElement;
|
|
2059
|
+
const containerRect = container.getBoundingClientRect();
|
|
2060
|
+
// Get mask dimensions from CSS properties
|
|
2061
|
+
const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
|
|
2062
|
+
const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
|
|
2063
|
+
const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
|
|
2064
|
+
const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
|
|
2065
|
+
// Convert percentages to actual video coordinates
|
|
2066
|
+
const videoWidth = this.videoRef.videoWidth;
|
|
2067
|
+
const videoHeight = this.videoRef.videoHeight;
|
|
2068
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
2069
|
+
const containerAspectRatio = containerRect.width / containerRect.height;
|
|
2070
|
+
let displayedVideoWidth, displayedVideoHeight;
|
|
2071
|
+
let videoOffsetX = 0, videoOffsetY = 0;
|
|
2072
|
+
if (videoAspectRatio > containerAspectRatio) {
|
|
2073
|
+
displayedVideoWidth = containerRect.width;
|
|
2074
|
+
displayedVideoHeight = containerRect.width / videoAspectRatio;
|
|
2075
|
+
videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
|
|
2076
|
+
}
|
|
2077
|
+
else {
|
|
2078
|
+
displayedVideoHeight = containerRect.height;
|
|
2079
|
+
displayedVideoWidth = containerRect.height * videoAspectRatio;
|
|
2080
|
+
videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
|
|
2081
|
+
}
|
|
2082
|
+
// Calculate mask coordinates in video space
|
|
2083
|
+
const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
|
|
2084
|
+
const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
|
|
2085
|
+
const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
|
|
2086
|
+
const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
|
|
2087
|
+
// Convert to video coordinates
|
|
2088
|
+
const scaleX = videoWidth / displayedVideoWidth;
|
|
2089
|
+
const scaleY = videoHeight / displayedVideoHeight;
|
|
2090
|
+
const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
|
|
2091
|
+
const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
|
|
2092
|
+
const maskWidthInVideo = maskWidthInContainer * scaleX;
|
|
2093
|
+
const maskHeightInVideo = maskHeightInContainer * scaleY;
|
|
2094
|
+
// Calculate crop coordinates
|
|
2095
|
+
const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
|
|
2096
|
+
const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
|
|
2097
|
+
const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
|
|
2098
|
+
const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
|
|
2099
|
+
// Use pooled canvas for cropped version
|
|
2100
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
2101
|
+
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
2102
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
2103
|
+
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
2104
|
+
const captureState = this.stateManager.getCaptureState();
|
|
2105
|
+
if (captureState.step === 'front') {
|
|
2106
|
+
this.stateManager.setCapturedImages({
|
|
2107
|
+
front: {
|
|
2108
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
2109
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
// Check if document classification is enabled (independent of detector)
|
|
2113
|
+
if (this.useDocumentClassification) {
|
|
2114
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
2115
|
+
if (classification && classification.class === 'passport') {
|
|
2116
|
+
this.completeProcess(true);
|
|
2117
|
+
this.returnCanvasToPool(captureCanvas);
|
|
2118
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
this.stateManager.updateCaptureState({
|
|
2123
|
+
step: 'back',
|
|
2124
|
+
isDetectionPaused: true,
|
|
2125
|
+
showFlipAnimation: true
|
|
2126
|
+
});
|
|
2127
|
+
setTimeout(() => {
|
|
2128
|
+
this.stateManager.updateCaptureState({
|
|
2129
|
+
showFlipAnimation: false,
|
|
2130
|
+
isDetectionPaused: false
|
|
2131
|
+
});
|
|
2132
|
+
this.startBackDocumentTimer();
|
|
2133
|
+
}, 3000);
|
|
2134
|
+
}
|
|
2135
|
+
else if (captureState.step === 'back') {
|
|
2136
|
+
this.stateManager.setCapturedImages({
|
|
2137
|
+
back: {
|
|
2138
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
2139
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
this.completeProcess(false);
|
|
2143
|
+
}
|
|
2144
|
+
// Return canvases to pool after use
|
|
2145
|
+
this.returnCanvasToPool(captureCanvas);
|
|
2146
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
2147
|
+
}
|
|
2058
2148
|
async takeScreenshot() {
|
|
2059
2149
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
2060
2150
|
return;
|
|
2061
|
-
this.logger.state('INICIANDO_CAPTURA', {
|
|
2062
|
-
captureStep: this.stateManager.getCaptureState().step,
|
|
2063
|
-
detectedBox: this.lastDetectedBox,
|
|
2064
|
-
videoResolution: {
|
|
2065
|
-
width: this.videoRef.videoWidth,
|
|
2066
|
-
height: this.videoRef.videoHeight
|
|
2067
|
-
}
|
|
2068
|
-
});
|
|
2069
2151
|
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
2070
2152
|
this.triggerCaptureAnimation();
|
|
2071
|
-
//
|
|
2072
|
-
const captureCanvas =
|
|
2073
|
-
captureCanvas.width = this.videoRef.videoWidth;
|
|
2074
|
-
captureCanvas.height = this.videoRef.videoHeight;
|
|
2153
|
+
// Use pooled canvas for optimization
|
|
2154
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
2075
2155
|
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
2156
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
2076
2157
|
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
2077
2158
|
// Calculate crop coordinates
|
|
2078
2159
|
const INPUT_SIZE = 320;
|
|
@@ -2082,11 +2163,10 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2082
2163
|
const cropY = Math.max(0, (this.lastDetectedBox.y * scaleY) - this.cropMargin);
|
|
2083
2164
|
const cropWidth = Math.min((this.lastDetectedBox.w * scaleX) + (2 * this.cropMargin), this.videoRef.videoWidth - cropX);
|
|
2084
2165
|
const cropHeight = Math.min((this.lastDetectedBox.h * scaleY) + (2 * this.cropMargin), this.videoRef.videoHeight - cropY);
|
|
2085
|
-
//
|
|
2086
|
-
const croppedCanvas =
|
|
2087
|
-
croppedCanvas.width = cropWidth;
|
|
2088
|
-
croppedCanvas.height = cropHeight;
|
|
2166
|
+
// Use pooled canvas for cropped version
|
|
2167
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
2089
2168
|
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
2169
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
2090
2170
|
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
2091
2171
|
const captureState = this.stateManager.getCaptureState();
|
|
2092
2172
|
if (captureState.step === 'front') {
|
|
@@ -2100,7 +2180,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2100
2180
|
if (this.useDocumentClassification) {
|
|
2101
2181
|
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
2102
2182
|
if (classification && classification.class === 'passport') {
|
|
2103
|
-
this.logger.state('PASAPORTE_DETECTADO_SALTANDO_REVERSO', { classification: classification?.class });
|
|
2104
2183
|
this.completeProcess(true);
|
|
2105
2184
|
return;
|
|
2106
2185
|
}
|
|
@@ -2127,6 +2206,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2127
2206
|
});
|
|
2128
2207
|
this.completeProcess(false);
|
|
2129
2208
|
}
|
|
2209
|
+
// Return canvases to pool after use
|
|
2210
|
+
this.returnCanvasToPool(captureCanvas);
|
|
2211
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
2130
2212
|
}
|
|
2131
2213
|
triggerCaptureAnimation() {
|
|
2132
2214
|
const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
|
|
@@ -2155,11 +2237,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2155
2237
|
setTimeout(() => {
|
|
2156
2238
|
this.stateManager.updateCaptureState({ showSuccessAnimation: false });
|
|
2157
2239
|
}, 3000);
|
|
2158
|
-
this.logger.state('PROCESO_COMPLETADO', {
|
|
2159
|
-
skippedBack,
|
|
2160
|
-
totalImages: capturedImages.metadata.totalImages,
|
|
2161
|
-
timestamp: new Date().toISOString()
|
|
2162
|
-
});
|
|
2163
2240
|
}
|
|
2164
2241
|
stopDetection() {
|
|
2165
2242
|
if (this.animationId) {
|
|
@@ -2168,7 +2245,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2168
2245
|
}
|
|
2169
2246
|
this.clearBackDocumentTimer();
|
|
2170
2247
|
this.detectionBoxes = [];
|
|
2171
|
-
this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
|
|
2172
2248
|
}
|
|
2173
2249
|
startBackDocumentTimer() {
|
|
2174
2250
|
if (!this.enableBackDocumentTimer)
|
|
@@ -2197,6 +2273,15 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2197
2273
|
return; // Don't toggle if switching camera
|
|
2198
2274
|
this.showCameraSelector = !this.showCameraSelector;
|
|
2199
2275
|
}
|
|
2276
|
+
async updateCameraInfoWithAutofocus() {
|
|
2277
|
+
try {
|
|
2278
|
+
const cameraInfo = await this.cameraService.getCameraInfo();
|
|
2279
|
+
this.cameraInfoWithAutofocus = cameraInfo;
|
|
2280
|
+
}
|
|
2281
|
+
catch (error) {
|
|
2282
|
+
// Keep existing state if update fails
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2200
2285
|
async handleCameraSwitch(cameraId) {
|
|
2201
2286
|
if (this.isSwitchingCamera)
|
|
2202
2287
|
return; // Prevent multiple simultaneous switches
|
|
@@ -2204,10 +2289,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2204
2289
|
// Close the selector immediately when user selects a camera
|
|
2205
2290
|
this.showCameraSelector = false;
|
|
2206
2291
|
this.isSwitchingCamera = true;
|
|
2207
|
-
this.logger.state('INICIANDO_CAMBIO_CAMARA', {
|
|
2208
|
-
from: this.cameraService.getSelectedCameraId(),
|
|
2209
|
-
to: cameraId
|
|
2210
|
-
});
|
|
2211
2292
|
// Stop current video stream
|
|
2212
2293
|
if (this.videoStream) {
|
|
2213
2294
|
this.videoStream.getTracks().forEach(track => track.stop());
|
|
@@ -2218,21 +2299,17 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2218
2299
|
const newStream = await this.cameraService.setupCamera();
|
|
2219
2300
|
// Update video element
|
|
2220
2301
|
await this.initializeVideoStream(newStream);
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
isRearCamera: this.cameraService.isRearCamera(newStream)
|
|
2224
|
-
});
|
|
2302
|
+
// Update camera info with autofocus data
|
|
2303
|
+
await this.updateCameraInfoWithAutofocus();
|
|
2225
2304
|
}
|
|
2226
2305
|
catch (error) {
|
|
2227
|
-
this.logger.error('Error al cambiar cámara:', error);
|
|
2228
2306
|
// Try to restore previous camera if switch failed
|
|
2229
2307
|
try {
|
|
2230
2308
|
const fallbackStream = await this.cameraService.setupCamera();
|
|
2231
2309
|
await this.initializeVideoStream(fallbackStream);
|
|
2232
2310
|
}
|
|
2233
2311
|
catch (fallbackError) {
|
|
2234
|
-
this.
|
|
2235
|
-
this.updateStatus('Error al cambiar cámara', 'No se pudo completar el cambio de dispositivo', 'error');
|
|
2312
|
+
this.updateStatus('Error al cambiar cámara', 'No se pudo cambiar el dispositivo', 'error');
|
|
2236
2313
|
}
|
|
2237
2314
|
}
|
|
2238
2315
|
finally {
|
|
@@ -2273,7 +2350,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2273
2350
|
this.stateManager.updateCaptureState({ isVideoActive: false, isLoading: false });
|
|
2274
2351
|
}
|
|
2275
2352
|
this.isMaskReady = false;
|
|
2276
|
-
this.updateStatus('
|
|
2353
|
+
this.updateStatus('Captura finalizada', '', 'ready');
|
|
2277
2354
|
this.detectionBoxes = [];
|
|
2278
2355
|
this.cleanup();
|
|
2279
2356
|
}
|
|
@@ -2284,7 +2361,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2284
2361
|
this.performanceUpdateInterval = window.setInterval(() => {
|
|
2285
2362
|
this.updatePerformanceMetrics();
|
|
2286
2363
|
}, 500);
|
|
2287
|
-
this.logger.debug('Monitor de performance inicializado');
|
|
2288
2364
|
}
|
|
2289
2365
|
updatePerformanceMetrics() {
|
|
2290
2366
|
const currentTime = performance.now();
|
|
@@ -2327,6 +2403,66 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2327
2403
|
if (detectionsFound > 0) {
|
|
2328
2404
|
this.performanceMetrics.successfulDetections++;
|
|
2329
2405
|
}
|
|
2406
|
+
// Update performance history for adaptive frame skipping
|
|
2407
|
+
this.performanceHistory.push(processingTime);
|
|
2408
|
+
if (this.performanceHistory.length > this.PERFORMANCE_HISTORY_SIZE) {
|
|
2409
|
+
this.performanceHistory.shift();
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
// Adaptive frame skipping based on performance
|
|
2413
|
+
getAdaptiveFrameSkip() {
|
|
2414
|
+
// Base conditions for high frame skip
|
|
2415
|
+
if (this.consecutiveFailures > 20)
|
|
2416
|
+
return this.MAX_FRAME_SKIP;
|
|
2417
|
+
if (this.performanceMetrics.inferenceTime > 200)
|
|
2418
|
+
return 6;
|
|
2419
|
+
if (this.performanceMetrics.memoryUsage > 200)
|
|
2420
|
+
return 5;
|
|
2421
|
+
// Calculate average processing time
|
|
2422
|
+
if (this.performanceHistory.length > 0) {
|
|
2423
|
+
const avgProcessingTime = this.performanceHistory.reduce((a, b) => a + b, 0) / this.performanceHistory.length;
|
|
2424
|
+
if (avgProcessingTime > 100)
|
|
2425
|
+
return 4;
|
|
2426
|
+
if (avgProcessingTime > 80)
|
|
2427
|
+
return 3;
|
|
2428
|
+
if (avgProcessingTime > 60)
|
|
2429
|
+
return this.BASE_FRAME_SKIP + 1;
|
|
2430
|
+
}
|
|
2431
|
+
// Low memory devices get higher frame skip
|
|
2432
|
+
if (this.performanceMetrics.memoryUsage > 150)
|
|
2433
|
+
return 4;
|
|
2434
|
+
// Performance is good, use base frame skip
|
|
2435
|
+
return this.BASE_FRAME_SKIP;
|
|
2436
|
+
}
|
|
2437
|
+
// Canvas pool management for screenshots
|
|
2438
|
+
getPooledCanvas(width, height) {
|
|
2439
|
+
// Try to find a canvas with matching dimensions
|
|
2440
|
+
const matchingIndex = this.canvasPool.findIndex(canvas => canvas.width === width && canvas.height === height);
|
|
2441
|
+
if (matchingIndex !== -1) {
|
|
2442
|
+
return this.canvasPool.splice(matchingIndex, 1)[0];
|
|
2443
|
+
}
|
|
2444
|
+
// If no matching canvas, try to reuse any canvas and resize
|
|
2445
|
+
if (this.canvasPool.length > 0) {
|
|
2446
|
+
const canvas = this.canvasPool.pop();
|
|
2447
|
+
canvas.width = width;
|
|
2448
|
+
canvas.height = height;
|
|
2449
|
+
return canvas;
|
|
2450
|
+
}
|
|
2451
|
+
// Create new canvas if pool is empty
|
|
2452
|
+
const canvas = document.createElement('canvas');
|
|
2453
|
+
canvas.width = width;
|
|
2454
|
+
canvas.height = height;
|
|
2455
|
+
return canvas;
|
|
2456
|
+
}
|
|
2457
|
+
returnCanvasToPool(canvas) {
|
|
2458
|
+
// Only return to pool if not at max capacity
|
|
2459
|
+
if (this.canvasPool.length < this.MAX_CANVAS_POOL_SIZE) {
|
|
2460
|
+
// Clear the canvas before returning to pool
|
|
2461
|
+
const ctx = canvas.getContext('2d');
|
|
2462
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
2463
|
+
this.canvasPool.push(canvas);
|
|
2464
|
+
}
|
|
2465
|
+
// If pool is full, let the canvas be garbage collected
|
|
2330
2466
|
}
|
|
2331
2467
|
static get style() { return myComponentCss; }
|
|
2332
2468
|
}, [1, "jaak-stamps", {
|
|
@@ -2335,6 +2471,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2335
2471
|
"maskSize": [2, "mask-size"],
|
|
2336
2472
|
"cropMargin": [2, "crop-margin"],
|
|
2337
2473
|
"useDocumentClassification": [4, "use-document-classification"],
|
|
2474
|
+
"useDocumentDetector": [4, "use-document-detector"],
|
|
2338
2475
|
"preferredCamera": [1, "preferred-camera"],
|
|
2339
2476
|
"captureDelay": [2, "capture-delay"],
|
|
2340
2477
|
"enableBackDocumentTimer": [4, "enable-back-document-timer"],
|
|
@@ -2346,9 +2483,11 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2346
2483
|
"showCameraSelector": [32],
|
|
2347
2484
|
"isSwitchingCamera": [32],
|
|
2348
2485
|
"hasDocumentDetected": [32],
|
|
2486
|
+
"cameraInfoWithAutofocus": [32],
|
|
2349
2487
|
"currentStatus": [32],
|
|
2350
2488
|
"performanceData": [32],
|
|
2351
2489
|
"backDocumentTimerRemaining": [32],
|
|
2490
|
+
"showManualCaptureButton": [32],
|
|
2352
2491
|
"getCapturedImages": [64],
|
|
2353
2492
|
"isProcessCompleted": [64],
|
|
2354
2493
|
"startCapture": [64],
|
|
@@ -2360,9 +2499,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
|
|
|
2360
2499
|
"getCameraInfo": [64],
|
|
2361
2500
|
"setPreferredCamera": [64],
|
|
2362
2501
|
"setCaptureDelay": [64],
|
|
2363
|
-
"getCaptureDelay": [64]
|
|
2364
|
-
"setTorchEnabled": [64],
|
|
2365
|
-
"focusAtPoint": [64]
|
|
2502
|
+
"getCaptureDelay": [64]
|
|
2366
2503
|
}]);
|
|
2367
2504
|
function defineCustomElement$1() {
|
|
2368
2505
|
if (typeof customElements === "undefined") {
|