@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
|
@@ -7,6 +7,7 @@ export class JaakStamps {
|
|
|
7
7
|
maskSize = 90;
|
|
8
8
|
cropMargin = 20;
|
|
9
9
|
useDocumentClassification = false;
|
|
10
|
+
useDocumentDetector = true;
|
|
10
11
|
preferredCamera = 'auto';
|
|
11
12
|
captureDelay = 1500;
|
|
12
13
|
enableBackDocumentTimer = false;
|
|
@@ -23,9 +24,16 @@ export class JaakStamps {
|
|
|
23
24
|
showCameraSelector = false;
|
|
24
25
|
isSwitchingCamera = false;
|
|
25
26
|
hasDocumentDetected = false;
|
|
27
|
+
cameraInfoWithAutofocus = {
|
|
28
|
+
availableCameras: [],
|
|
29
|
+
selectedCameraId: null,
|
|
30
|
+
deviceType: 'desktop',
|
|
31
|
+
isMultipleCamerasAvailable: false,
|
|
32
|
+
preferredFacing: null
|
|
33
|
+
};
|
|
26
34
|
currentStatus = {
|
|
27
|
-
message: '
|
|
28
|
-
description: 'Configurando
|
|
35
|
+
message: 'Preparando capturador...',
|
|
36
|
+
description: 'Configurando herramientas de captura',
|
|
29
37
|
type: 'initializing',
|
|
30
38
|
isInitialized: false
|
|
31
39
|
};
|
|
@@ -40,9 +48,9 @@ export class JaakStamps {
|
|
|
40
48
|
detectionRate: 0
|
|
41
49
|
};
|
|
42
50
|
backDocumentTimerRemaining = 0;
|
|
51
|
+
showManualCaptureButton = false;
|
|
43
52
|
// Services
|
|
44
53
|
serviceContainer;
|
|
45
|
-
logger;
|
|
46
54
|
eventBus;
|
|
47
55
|
stateManager;
|
|
48
56
|
cameraService;
|
|
@@ -73,19 +81,25 @@ export class JaakStamps {
|
|
|
73
81
|
lastUpdateTime: 0
|
|
74
82
|
};
|
|
75
83
|
performanceUpdateInterval;
|
|
76
|
-
// Performance optimization
|
|
84
|
+
// Performance optimization with adaptive frame skipping
|
|
77
85
|
frameSkipCounter = 0;
|
|
78
|
-
|
|
86
|
+
BASE_FRAME_SKIP = 2;
|
|
87
|
+
MAX_FRAME_SKIP = 8;
|
|
79
88
|
consecutiveFailures = 0;
|
|
80
89
|
MAX_FAILURES = 30;
|
|
81
90
|
lastInferenceTime = 0;
|
|
82
91
|
MIN_INFERENCE_INTERVAL = 50;
|
|
92
|
+
performanceHistory = [];
|
|
93
|
+
PERFORMANCE_HISTORY_SIZE = 10;
|
|
94
|
+
// Canvas pool for optimized screenshot capture
|
|
95
|
+
canvasPool = [];
|
|
96
|
+
MAX_CANVAS_POOL_SIZE = 3;
|
|
83
97
|
async componentDidLoad() {
|
|
84
|
-
this.updateStatus('
|
|
98
|
+
this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
|
|
85
99
|
await this.initializeServices();
|
|
86
|
-
this.updateStatus('
|
|
100
|
+
this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
|
|
87
101
|
await this.setupEventListeners();
|
|
88
|
-
this.updateStatus('
|
|
102
|
+
this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
|
|
89
103
|
await this.initializeComponent();
|
|
90
104
|
if (this.debug) {
|
|
91
105
|
this.initializePerformanceMonitor();
|
|
@@ -98,37 +112,28 @@ export class JaakStamps {
|
|
|
98
112
|
maskSize: this.maskSize,
|
|
99
113
|
cropMargin: this.cropMargin,
|
|
100
114
|
useDocumentClassification: this.useDocumentClassification,
|
|
115
|
+
useDocumentDetector: this.useDocumentDetector,
|
|
101
116
|
preferredCamera: this.preferredCamera,
|
|
102
117
|
captureDelay: this.captureDelay
|
|
103
118
|
};
|
|
104
119
|
this.serviceContainer = new ServiceContainer(config);
|
|
105
|
-
this.logger = this.serviceContainer.getLogger();
|
|
106
120
|
this.eventBus = this.serviceContainer.getEventBus();
|
|
107
121
|
this.stateManager = this.serviceContainer.getStateManager();
|
|
108
122
|
this.cameraService = this.serviceContainer.getCameraService();
|
|
109
123
|
this.detectionService = this.serviceContainer.getDetectionService();
|
|
110
|
-
this.logger.state('SERVICIOS_INICIALIZADOS', { timestamp: Date.now() });
|
|
111
124
|
}
|
|
112
125
|
async setupEventListeners() {
|
|
113
126
|
this.eventBus.on('state-changed', (data) => {
|
|
114
127
|
this.handleStateChange(data);
|
|
115
128
|
});
|
|
116
|
-
this.eventBus.on('camera-changed', (
|
|
117
|
-
|
|
129
|
+
this.eventBus.on('camera-changed', () => {
|
|
130
|
+
// Camera changed event
|
|
118
131
|
});
|
|
119
|
-
this.eventBus.on('error', (
|
|
120
|
-
|
|
132
|
+
this.eventBus.on('error', () => {
|
|
133
|
+
// Handle service errors
|
|
121
134
|
});
|
|
122
135
|
}
|
|
123
136
|
async initializeComponent() {
|
|
124
|
-
this.logger.state('COMPONENTE_INICIALIZANDO', {
|
|
125
|
-
debug: this.debug,
|
|
126
|
-
maskSize: this.maskSize,
|
|
127
|
-
cropMargin: this.cropMargin,
|
|
128
|
-
useDocumentClassification: this.useDocumentClassification,
|
|
129
|
-
preferredCamera: this.preferredCamera,
|
|
130
|
-
captureDelay: this.captureDelay
|
|
131
|
-
});
|
|
132
137
|
this.validateProps();
|
|
133
138
|
if (this.debug) {
|
|
134
139
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
@@ -137,58 +142,67 @@ export class JaakStamps {
|
|
|
137
142
|
this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura', 'initializing');
|
|
138
143
|
await this.cameraService.detectDeviceType();
|
|
139
144
|
await this.cameraService.enumerateDevices();
|
|
140
|
-
this.
|
|
145
|
+
await this.updateCameraInfoWithAutofocus();
|
|
146
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'initializing');
|
|
141
147
|
await this.loadOnnxRuntime();
|
|
142
148
|
this.initializeResizeObserver();
|
|
143
149
|
}
|
|
144
150
|
validateProps() {
|
|
145
151
|
if (this.maskSize < 50 || this.maskSize > 100) {
|
|
146
|
-
this.logger.warn(`Propiedad maskSize inválida. Valor: ${this.maskSize}, esperado: 50-100. Usando valor por defecto: 90`);
|
|
147
152
|
this.maskSize = 90;
|
|
148
153
|
}
|
|
149
154
|
if (this.cropMargin < 0 || this.cropMargin > 100) {
|
|
150
|
-
this.logger.warn(`Propiedad cropMargin inválida. Valor: ${this.cropMargin}, esperado: 0-100. Usando valor por defecto: 0`);
|
|
151
155
|
this.cropMargin = 0;
|
|
152
156
|
}
|
|
153
157
|
const validOptions = ['auto', 'front', 'back'];
|
|
154
158
|
if (!validOptions.includes(this.preferredCamera)) {
|
|
155
|
-
this.logger.warn(`Propiedad preferredCamera inválida. Valor: ${this.preferredCamera}, esperado: ${validOptions.join(', ')}. Usando valor por defecto: 'auto'`);
|
|
156
159
|
this.preferredCamera = 'auto';
|
|
157
160
|
}
|
|
158
161
|
if (this.captureDelay < 0 || this.captureDelay > 10000) {
|
|
159
|
-
this.logger.warn(`Propiedad captureDelay inválida. Valor: ${this.captureDelay}, esperado: 0-10000. Usando valor por defecto: 1500`);
|
|
160
162
|
this.captureDelay = 1500;
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
async loadOnnxRuntime() {
|
|
164
166
|
if (!window.ort) {
|
|
165
|
-
this.updateStatus('
|
|
167
|
+
this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
|
|
166
168
|
const script = document.createElement('script');
|
|
167
169
|
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
|
|
168
170
|
document.head.appendChild(script);
|
|
169
171
|
await new Promise((resolve) => {
|
|
170
172
|
script.onload = () => {
|
|
171
|
-
setTimeout(() => {
|
|
172
|
-
this.finalizeInitialization();
|
|
173
|
+
setTimeout(async () => {
|
|
174
|
+
await this.finalizeInitialization();
|
|
173
175
|
resolve(undefined);
|
|
174
176
|
}, 300);
|
|
175
177
|
};
|
|
176
178
|
});
|
|
177
179
|
}
|
|
178
180
|
else {
|
|
179
|
-
setTimeout(() => {
|
|
180
|
-
this.finalizeInitialization();
|
|
181
|
+
setTimeout(async () => {
|
|
182
|
+
await this.finalizeInitialization();
|
|
181
183
|
}, 300);
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
|
-
finalizeInitialization() {
|
|
186
|
+
async finalizeInitialization() {
|
|
185
187
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
// Check camera permissions before showing "ready" status
|
|
189
|
+
const hasPermissions = await this.checkCameraPermissions();
|
|
190
|
+
if (hasPermissions) {
|
|
191
|
+
this.currentStatus = {
|
|
192
|
+
message: 'Listo para capturar',
|
|
193
|
+
description: '',
|
|
194
|
+
type: 'ready',
|
|
195
|
+
isInitialized: true
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
this.currentStatus = {
|
|
200
|
+
message: 'Permisos de cámara requeridos',
|
|
201
|
+
description: 'Por favor, otorgue permisos de cámara para continuar',
|
|
202
|
+
type: 'error',
|
|
203
|
+
isInitialized: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
192
206
|
this.emitReadyEvent();
|
|
193
207
|
}
|
|
194
208
|
updateStatus(message, description, type = 'loading') {
|
|
@@ -202,11 +216,26 @@ export class JaakStamps {
|
|
|
202
216
|
emitReadyEvent() {
|
|
203
217
|
const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
|
|
204
218
|
this.isReady.emit(isDocumentReady);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
}
|
|
220
|
+
async checkCameraPermissions() {
|
|
221
|
+
try {
|
|
222
|
+
if (!navigator.permissions) {
|
|
223
|
+
// Fallback: try to access camera directly
|
|
224
|
+
try {
|
|
225
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
226
|
+
stream.getTracks().forEach(track => track.stop());
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
234
|
+
return permission.state === 'granted';
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
210
239
|
}
|
|
211
240
|
handleStateChange(data) {
|
|
212
241
|
// React to state changes from StateManager
|
|
@@ -229,9 +258,31 @@ export class JaakStamps {
|
|
|
229
258
|
const container = this.detectionContainer.parentElement;
|
|
230
259
|
const rect = container.getBoundingClientRect();
|
|
231
260
|
this.updateMaskDimensions(rect);
|
|
232
|
-
this.logger.debug('Container redimensionado:', { width: rect.width, height: rect.height });
|
|
233
261
|
}
|
|
234
262
|
}
|
|
263
|
+
getGuideText(step) {
|
|
264
|
+
if (step === 'completed') {
|
|
265
|
+
return 'Proceso completado';
|
|
266
|
+
}
|
|
267
|
+
if (step === 'front') {
|
|
268
|
+
// Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
|
|
269
|
+
if (this.showManualCaptureButton) {
|
|
270
|
+
return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
|
|
271
|
+
}
|
|
272
|
+
// Instrucción básica de alineación
|
|
273
|
+
return 'Alinee su identificación con el marco';
|
|
274
|
+
}
|
|
275
|
+
else if (step === 'back') {
|
|
276
|
+
// Para el reverso, mostrar instrucciones específicas según el modo
|
|
277
|
+
if (this.useDocumentDetector) {
|
|
278
|
+
return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return 'Alinee su identificación con el marco';
|
|
285
|
+
}
|
|
235
286
|
updateMaskDimensions(containerRect) {
|
|
236
287
|
if (!this.videoRef)
|
|
237
288
|
return;
|
|
@@ -283,16 +334,38 @@ export class JaakStamps {
|
|
|
283
334
|
this.el.style.setProperty('--mask-center-x', `${videoCenterXPercent}%`);
|
|
284
335
|
this.el.style.setProperty('--mask-center-y', `${videoCenterYPercent}%`);
|
|
285
336
|
this.isMaskReady = true;
|
|
286
|
-
this.logger.state('DIMENSIONES_MASCARA_ACTUALIZADAS', {
|
|
287
|
-
video: { width: videoWidth, height: videoHeight },
|
|
288
|
-
displayed: { width: displayedVideoWidth, height: displayedVideoHeight },
|
|
289
|
-
mask: { widthPercent: maskWidthPercent, heightPercent: maskHeightPercent },
|
|
290
|
-
center: { x: videoCenterXPercent, y: videoCenterYPercent },
|
|
291
|
-
offset: { x: videoOffsetX, y: videoOffsetY }
|
|
292
|
-
});
|
|
293
337
|
}
|
|
294
338
|
// PUBLIC METHODS
|
|
339
|
+
// Helper method to check if component is ready for operations
|
|
340
|
+
isComponentReady() {
|
|
341
|
+
if (!this.currentStatus.isInitialized) {
|
|
342
|
+
return {
|
|
343
|
+
ready: false,
|
|
344
|
+
message: 'El componente aún se está inicializando',
|
|
345
|
+
status: 'initializing'
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') {
|
|
349
|
+
return {
|
|
350
|
+
ready: false,
|
|
351
|
+
message: 'El componente está cargando recursos',
|
|
352
|
+
status: 'loading'
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (!this.serviceContainer || !this.stateManager || !this.cameraService || !this.detectionService) {
|
|
356
|
+
return {
|
|
357
|
+
ready: false,
|
|
358
|
+
message: 'Los servicios del componente no están disponibles',
|
|
359
|
+
status: 'error'
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return { ready: true };
|
|
363
|
+
}
|
|
295
364
|
async getCapturedImages() {
|
|
365
|
+
const readyCheck = this.isComponentReady();
|
|
366
|
+
if (!readyCheck.ready) {
|
|
367
|
+
throw new Error(readyCheck.message);
|
|
368
|
+
}
|
|
296
369
|
const state = this.stateManager.getCaptureState();
|
|
297
370
|
if (state.step !== 'completed') {
|
|
298
371
|
throw new Error('El proceso de captura no ha sido completado');
|
|
@@ -300,47 +373,128 @@ export class JaakStamps {
|
|
|
300
373
|
return this.stateManager.getCapturedImages();
|
|
301
374
|
}
|
|
302
375
|
async isProcessCompleted() {
|
|
376
|
+
const readyCheck = this.isComponentReady();
|
|
377
|
+
if (!readyCheck.ready) {
|
|
378
|
+
return false; // Return false instead of error for this method
|
|
379
|
+
}
|
|
303
380
|
return this.stateManager.isProcessCompleted();
|
|
304
381
|
}
|
|
305
382
|
async startCapture() {
|
|
306
|
-
|
|
383
|
+
const readyCheck = this.isComponentReady();
|
|
384
|
+
if (!readyCheck.ready) {
|
|
385
|
+
this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
|
|
386
|
+
return { success: false, error: readyCheck.message };
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
await this.startDetection();
|
|
390
|
+
return { success: true };
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
this.updateStatus('Error al iniciar captura', error.message, 'error');
|
|
394
|
+
return { success: false, error: error.message };
|
|
395
|
+
}
|
|
307
396
|
}
|
|
308
397
|
async stopCapture() {
|
|
309
|
-
this.
|
|
398
|
+
const readyCheck = this.isComponentReady();
|
|
399
|
+
if (!readyCheck.ready) {
|
|
400
|
+
return { success: false, error: readyCheck.message };
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
this.exitSession();
|
|
404
|
+
return { success: true };
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
return { success: false, error: error.message };
|
|
408
|
+
}
|
|
310
409
|
}
|
|
311
410
|
async resetCapture() {
|
|
312
|
-
this.
|
|
411
|
+
const readyCheck = this.isComponentReady();
|
|
412
|
+
if (!readyCheck.ready) {
|
|
413
|
+
return { success: false, error: readyCheck.message };
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
this.resetDetection();
|
|
417
|
+
return { success: true };
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
return { success: false, error: error.message };
|
|
421
|
+
}
|
|
313
422
|
}
|
|
314
423
|
async skipBackCapture() {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
424
|
+
const readyCheck = this.isComponentReady();
|
|
425
|
+
if (!readyCheck.ready) {
|
|
426
|
+
return { success: false, error: readyCheck.message };
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
const captureState = this.stateManager.getCaptureState();
|
|
430
|
+
const capturedImages = this.stateManager.getCapturedImages();
|
|
431
|
+
if (captureState.step === 'back' && capturedImages.front.fullFrame) {
|
|
432
|
+
this.completeProcess(true);
|
|
433
|
+
return { success: true };
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
return { success: false, error: 'No se puede saltar el reverso en el estado actual' };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
return { success: false, error: error.message };
|
|
319
441
|
}
|
|
320
442
|
}
|
|
321
443
|
async getStatus() {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
444
|
+
const readyCheck = this.isComponentReady();
|
|
445
|
+
if (!readyCheck.ready) {
|
|
446
|
+
return {
|
|
447
|
+
isVideoActive: false,
|
|
448
|
+
captureStep: 'front',
|
|
449
|
+
hasImages: false,
|
|
450
|
+
isProcessCompleted: false,
|
|
451
|
+
isModelPreloaded: false,
|
|
452
|
+
componentStatus: readyCheck.status,
|
|
453
|
+
componentMessage: readyCheck.message
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
const captureState = this.stateManager.getCaptureState();
|
|
458
|
+
const capturedImages = this.stateManager.getCapturedImages();
|
|
459
|
+
return {
|
|
460
|
+
isVideoActive: captureState.isVideoActive,
|
|
461
|
+
captureStep: captureState.step,
|
|
462
|
+
hasImages: !!(capturedImages.front.fullFrame || capturedImages.back.fullFrame),
|
|
463
|
+
isProcessCompleted: this.stateManager.isProcessCompleted(),
|
|
464
|
+
isModelPreloaded: this.detectionService.isModelLoaded(),
|
|
465
|
+
componentStatus: 'ready'
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
return {
|
|
470
|
+
isVideoActive: false,
|
|
471
|
+
captureStep: 'front',
|
|
472
|
+
hasImages: false,
|
|
473
|
+
isProcessCompleted: false,
|
|
474
|
+
isModelPreloaded: false,
|
|
475
|
+
componentStatus: 'error',
|
|
476
|
+
componentMessage: error.message
|
|
477
|
+
};
|
|
478
|
+
}
|
|
331
479
|
}
|
|
332
480
|
async preloadModel() {
|
|
481
|
+
const readyCheck = this.isComponentReady();
|
|
482
|
+
if (!readyCheck.ready) {
|
|
483
|
+
this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
|
|
484
|
+
return { success: false, error: readyCheck.message };
|
|
485
|
+
}
|
|
333
486
|
if (this.detectionService.isModelLoaded()) {
|
|
334
|
-
this.
|
|
335
|
-
this.updateStatus('Modelos ya cargados', '', 'ready');
|
|
487
|
+
this.updateStatus('Reconocimiento listo', '', 'ready');
|
|
336
488
|
return { success: true, message: 'Model already loaded' };
|
|
337
489
|
}
|
|
338
490
|
try {
|
|
339
491
|
const loadStartTime = performance.now();
|
|
340
|
-
this.updateStatus('
|
|
341
|
-
this.stateManager
|
|
492
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
|
|
493
|
+
if (this.stateManager) {
|
|
494
|
+
this.stateManager.updateCaptureState({ isLoading: true });
|
|
495
|
+
}
|
|
342
496
|
await this.detectionService.loadModel();
|
|
343
|
-
this.updateStatus('
|
|
497
|
+
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
344
498
|
await this.detectionService.loadClassificationModel();
|
|
345
499
|
const loadEndTime = performance.now();
|
|
346
500
|
const loadTime = loadEndTime - loadStartTime;
|
|
@@ -348,80 +502,118 @@ export class JaakStamps {
|
|
|
348
502
|
if (this.debug) {
|
|
349
503
|
this.recordOnnxPerformance(loadTime, 0);
|
|
350
504
|
}
|
|
351
|
-
this.updateStatus('
|
|
505
|
+
this.updateStatus('Finalizando configuración...', 'Preparando herramientas para captura', 'loading');
|
|
352
506
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
353
|
-
this.updateStatus('
|
|
354
|
-
this.stateManager
|
|
507
|
+
this.updateStatus('Reconocimiento preparado', '', 'ready');
|
|
508
|
+
if (this.stateManager) {
|
|
509
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
510
|
+
}
|
|
355
511
|
this.emitReadyEvent();
|
|
356
|
-
this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', { loadTime: Math.round(loadTime) });
|
|
357
512
|
return { success: true, message: 'Models preloaded successfully' };
|
|
358
513
|
}
|
|
359
514
|
catch (error) {
|
|
360
|
-
this.
|
|
361
|
-
this.
|
|
362
|
-
|
|
515
|
+
this.updateStatus('Error de configuración', 'No se pudieron preparar las herramientas necesarias', 'error');
|
|
516
|
+
if (this.stateManager) {
|
|
517
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
518
|
+
}
|
|
363
519
|
return { success: false, error: error.message };
|
|
364
520
|
}
|
|
365
521
|
}
|
|
366
522
|
async getCameraInfo() {
|
|
367
|
-
|
|
523
|
+
const readyCheck = this.isComponentReady();
|
|
524
|
+
if (!readyCheck.ready) {
|
|
525
|
+
return {
|
|
526
|
+
availableCameras: [],
|
|
527
|
+
selectedCameraId: null,
|
|
528
|
+
deviceType: 'desktop',
|
|
529
|
+
isMultipleCamerasAvailable: false,
|
|
530
|
+
preferredFacing: null,
|
|
531
|
+
error: readyCheck.message
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
return await this.cameraService.getCameraInfo();
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
return {
|
|
539
|
+
availableCameras: [],
|
|
540
|
+
selectedCameraId: null,
|
|
541
|
+
deviceType: 'desktop',
|
|
542
|
+
isMultipleCamerasAvailable: false,
|
|
543
|
+
preferredFacing: null,
|
|
544
|
+
error: error.message
|
|
545
|
+
};
|
|
546
|
+
}
|
|
368
547
|
}
|
|
369
548
|
async setPreferredCamera(camera) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
549
|
+
const readyCheck = this.isComponentReady();
|
|
550
|
+
if (!readyCheck.ready) {
|
|
551
|
+
return {
|
|
552
|
+
success: false,
|
|
553
|
+
error: readyCheck.message,
|
|
554
|
+
selectedCamera: null,
|
|
555
|
+
availableCameras: 0
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
this.preferredCamera = camera;
|
|
560
|
+
this.serviceContainer.updateConfig({ preferredCamera: camera });
|
|
561
|
+
await this.cameraService.enumerateDevices();
|
|
562
|
+
await this.updateCameraInfoWithAutofocus();
|
|
563
|
+
const captureState = this.stateManager.getCaptureState();
|
|
564
|
+
if (captureState.isVideoActive) {
|
|
565
|
+
const selectedCameraId = this.cameraService.getSelectedCameraId();
|
|
566
|
+
if (selectedCameraId) {
|
|
567
|
+
await this.cameraService.switchCamera(selectedCameraId);
|
|
568
|
+
}
|
|
378
569
|
}
|
|
570
|
+
return {
|
|
571
|
+
success: true,
|
|
572
|
+
selectedCamera: this.cameraService.getSelectedCameraId(),
|
|
573
|
+
availableCameras: this.cameraService.getAvailableCameras().length
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
return {
|
|
578
|
+
success: false,
|
|
579
|
+
error: error.message,
|
|
580
|
+
selectedCamera: null,
|
|
581
|
+
availableCameras: 0
|
|
582
|
+
};
|
|
379
583
|
}
|
|
380
|
-
return {
|
|
381
|
-
success: true,
|
|
382
|
-
selectedCamera: this.cameraService.getSelectedCameraId(),
|
|
383
|
-
availableCameras: this.cameraService.getAvailableCameras().length
|
|
384
|
-
};
|
|
385
584
|
}
|
|
386
585
|
async setCaptureDelay(delay) {
|
|
387
586
|
if (delay < 0 || delay > 10000) {
|
|
388
|
-
|
|
587
|
+
return {
|
|
588
|
+
success: false,
|
|
589
|
+
error: 'Capture delay must be between 0 and 10000 milliseconds',
|
|
590
|
+
captureDelay: this.captureDelay
|
|
591
|
+
};
|
|
389
592
|
}
|
|
593
|
+
// Allow setting capture delay even during initialization
|
|
390
594
|
this.captureDelay = delay;
|
|
391
|
-
this.serviceContainer
|
|
595
|
+
if (this.serviceContainer) {
|
|
596
|
+
this.serviceContainer.updateConfig({ captureDelay: delay });
|
|
597
|
+
}
|
|
392
598
|
return {
|
|
393
599
|
success: true,
|
|
394
600
|
captureDelay: this.captureDelay
|
|
395
601
|
};
|
|
396
602
|
}
|
|
397
603
|
async getCaptureDelay() {
|
|
604
|
+
// Always allow getting capture delay, even during initialization
|
|
398
605
|
return this.captureDelay;
|
|
399
606
|
}
|
|
400
|
-
async setTorchEnabled(enabled) {
|
|
401
|
-
const torchEnabled = await this.cameraService.setTorchEnabled(enabled, this.videoStream);
|
|
402
|
-
return {
|
|
403
|
-
success: torchEnabled,
|
|
404
|
-
enabled: torchEnabled ? enabled : false
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
async focusAtPoint(x, y) {
|
|
408
|
-
const focused = await this.cameraService.focusAtPoint(x, y, this.videoStream);
|
|
409
|
-
return {
|
|
410
|
-
success: focused,
|
|
411
|
-
coordinates: { x, y }
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
607
|
// DETECTION METHODS
|
|
415
608
|
async startDetection() {
|
|
416
|
-
this.logger.state('INICIANDO_DETECCION');
|
|
417
609
|
try {
|
|
418
610
|
// Paso 1: Verificar y cargar modelos si es necesario
|
|
419
611
|
if (!this.detectionService.isModelLoaded()) {
|
|
420
612
|
const loadStartTime = performance.now();
|
|
421
|
-
this.updateStatus('
|
|
613
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
|
|
422
614
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
423
615
|
await this.detectionService.loadModel();
|
|
424
|
-
this.updateStatus('
|
|
616
|
+
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
425
617
|
await this.detectionService.loadClassificationModel();
|
|
426
618
|
const loadEndTime = performance.now();
|
|
427
619
|
const loadTime = loadEndTime - loadStartTime;
|
|
@@ -429,16 +621,21 @@ export class JaakStamps {
|
|
|
429
621
|
if (this.debug) {
|
|
430
622
|
this.recordOnnxPerformance(loadTime, 0);
|
|
431
623
|
}
|
|
432
|
-
this.logger.state('MODELOS_CARGADOS_EN_DETECCION', { loadTime: Math.round(loadTime) });
|
|
433
624
|
}
|
|
434
625
|
// Paso 2: Detectar y configurar dispositivos
|
|
435
|
-
this.updateStatus('
|
|
626
|
+
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
436
627
|
await this.cameraService.enumerateDevices();
|
|
628
|
+
await this.updateCameraInfoWithAutofocus();
|
|
437
629
|
// Paso 3: Configurar cámara seleccionada
|
|
438
|
-
this.updateStatus('
|
|
630
|
+
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
439
631
|
const stream = await this.cameraService.setupCamera();
|
|
440
632
|
// Paso 4: Inicializar video y ocultar estado inmediatamente
|
|
441
|
-
this.
|
|
633
|
+
if (this.useDocumentDetector) {
|
|
634
|
+
this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
638
|
+
}
|
|
442
639
|
await this.initializeVideoStream(stream);
|
|
443
640
|
// Paso 5: Calibrar máscara
|
|
444
641
|
if (this.detectionContainer) {
|
|
@@ -449,11 +646,14 @@ export class JaakStamps {
|
|
|
449
646
|
// Finalizar e iniciar captura
|
|
450
647
|
this.startTime = Date.now();
|
|
451
648
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
649
|
+
// Mostrar botón manual si el detector está deshabilitado
|
|
650
|
+
if (!this.useDocumentDetector) {
|
|
651
|
+
this.showManualCaptureButton = true;
|
|
652
|
+
}
|
|
452
653
|
this.detectFrame();
|
|
453
654
|
}
|
|
454
655
|
catch (err) {
|
|
455
|
-
this.
|
|
456
|
-
this.updateStatus('Error al iniciar captura', 'No se pudo completar la inicialización', 'error');
|
|
656
|
+
this.updateStatus('Error al iniciar', 'No se pudo preparar la captura', 'error');
|
|
457
657
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
458
658
|
}
|
|
459
659
|
}
|
|
@@ -463,10 +663,6 @@ export class JaakStamps {
|
|
|
463
663
|
this.videoStream = stream;
|
|
464
664
|
const isRear = this.cameraService.isRearCamera(stream);
|
|
465
665
|
this.shouldMirrorVideo = !isRear;
|
|
466
|
-
this.logger.state('CAMARA_CONFIGURADA', {
|
|
467
|
-
isRearCamera: isRear,
|
|
468
|
-
shouldMirrorVideo: this.shouldMirrorVideo
|
|
469
|
-
});
|
|
470
666
|
return new Promise((resolve) => {
|
|
471
667
|
this.videoRef.onloadedmetadata = async () => {
|
|
472
668
|
await this.videoRef.play();
|
|
@@ -485,15 +681,28 @@ export class JaakStamps {
|
|
|
485
681
|
const captureState = this.stateManager.getCaptureState();
|
|
486
682
|
if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
|
|
487
683
|
return;
|
|
684
|
+
// Show manual capture button when document detector is disabled
|
|
685
|
+
if (!this.useDocumentDetector) {
|
|
686
|
+
this.showManualCaptureButton = true;
|
|
687
|
+
// Continue the loop for UI updates but skip detection logic
|
|
688
|
+
if (captureState.step !== 'completed') {
|
|
689
|
+
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
690
|
+
}
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.showManualCaptureButton = false;
|
|
695
|
+
}
|
|
488
696
|
if (captureState.isDetectionPaused) {
|
|
489
697
|
if (captureState.step !== 'completed') {
|
|
490
698
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
491
699
|
}
|
|
492
700
|
return;
|
|
493
701
|
}
|
|
494
|
-
//
|
|
702
|
+
// Adaptive frame skipping for performance
|
|
495
703
|
this.frameSkipCounter++;
|
|
496
|
-
|
|
704
|
+
const currentFrameSkip = this.getAdaptiveFrameSkip();
|
|
705
|
+
if (this.frameSkipCounter <= currentFrameSkip) {
|
|
497
706
|
if (captureState.step !== 'completed') {
|
|
498
707
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
499
708
|
}
|
|
@@ -569,7 +778,6 @@ export class JaakStamps {
|
|
|
569
778
|
}
|
|
570
779
|
}
|
|
571
780
|
catch (e) {
|
|
572
|
-
this.logger.error('Error en inferencia de modelo:', e);
|
|
573
781
|
const captureState = this.stateManager.getCaptureState();
|
|
574
782
|
if (captureState.step !== 'completed') {
|
|
575
783
|
setTimeout(() => this.detectFrame(), 100);
|
|
@@ -596,6 +804,8 @@ export class JaakStamps {
|
|
|
596
804
|
clearInterval(this.performanceUpdateInterval);
|
|
597
805
|
this.performanceUpdateInterval = undefined;
|
|
598
806
|
}
|
|
807
|
+
// Clear canvas pool
|
|
808
|
+
this.canvasPool = [];
|
|
599
809
|
this.detectionBoxes = [];
|
|
600
810
|
this.alignmentStartTime = undefined;
|
|
601
811
|
this.hasDocumentDetected = false;
|
|
@@ -610,14 +820,8 @@ export class JaakStamps {
|
|
|
610
820
|
step: 'front',
|
|
611
821
|
isCapturing: false
|
|
612
822
|
};
|
|
613
|
-
const cameraInfo = this.
|
|
614
|
-
|
|
615
|
-
isMultipleCamerasAvailable: false,
|
|
616
|
-
selectedCameraId: null,
|
|
617
|
-
deviceType: 'desktop',
|
|
618
|
-
preferredFacing: null
|
|
619
|
-
};
|
|
620
|
-
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: {
|
|
823
|
+
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
824
|
+
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: {
|
|
621
825
|
position: 'absolute',
|
|
622
826
|
left: `${box.x}px`,
|
|
623
827
|
top: `${box.y}px`,
|
|
@@ -626,9 +830,9 @@ export class JaakStamps {
|
|
|
626
830
|
border: '2px solid #32406C',
|
|
627
831
|
pointerEvents: 'none',
|
|
628
832
|
boxSizing: 'border-box'
|
|
629
|
-
} })))), this.isMaskReady && (h("div", { key: '
|
|
833
|
+
} })))), 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
|
|
630
834
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
631
|
-
: 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: '
|
|
835
|
+
: '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" })))));
|
|
632
836
|
}
|
|
633
837
|
// Utility methods
|
|
634
838
|
updateDetectionBoxes(boxes) {
|
|
@@ -709,35 +913,18 @@ export class JaakStamps {
|
|
|
709
913
|
if (allSidesAligned && bestBox) {
|
|
710
914
|
cardOutline?.classList.add('perfect-match');
|
|
711
915
|
corners?.forEach(corner => corner.classList.add('perfect-match'));
|
|
712
|
-
// Debug logging
|
|
713
|
-
this.logger.state('CAPTURE_EVALUATION', {
|
|
714
|
-
allSidesAligned: true,
|
|
715
|
-
hasScreenshotTaken: this.hasScreenshotTaken,
|
|
716
|
-
captureDelay: this.captureDelay,
|
|
717
|
-
alignmentStartTime: this.alignmentStartTime
|
|
718
|
-
});
|
|
719
916
|
if (!this.hasScreenshotTaken) {
|
|
720
917
|
const currentTime = Date.now();
|
|
721
918
|
// Initialize alignment start time if not set
|
|
722
919
|
if (!this.alignmentStartTime) {
|
|
723
920
|
this.alignmentStartTime = currentTime;
|
|
724
|
-
this.logger.state('ALIGNMENT_TIMER_STARTED', { startTime: currentTime });
|
|
725
921
|
}
|
|
726
922
|
// Check if document has been aligned for the configured delay
|
|
727
923
|
const alignmentDuration = currentTime - this.alignmentStartTime;
|
|
728
|
-
this.logger.state('ALIGNMENT_DURATION_CHECK', {
|
|
729
|
-
alignmentDuration,
|
|
730
|
-
captureDelay: this.captureDelay,
|
|
731
|
-
readyToCapture: alignmentDuration >= this.captureDelay
|
|
732
|
-
});
|
|
733
924
|
if (alignmentDuration >= this.captureDelay) {
|
|
734
|
-
this.logger.state('TRIGGERING_CAPTURE', {
|
|
735
|
-
alignmentDuration,
|
|
736
|
-
captureDelay: this.captureDelay
|
|
737
|
-
});
|
|
738
925
|
this.lastDetectedBox = bestBox;
|
|
739
|
-
this.takeScreenshot().catch(
|
|
740
|
-
|
|
926
|
+
this.takeScreenshot().catch(() => {
|
|
927
|
+
// Handle screenshot error silently
|
|
741
928
|
});
|
|
742
929
|
this.hasScreenshotTaken = true;
|
|
743
930
|
this.alignmentStartTime = undefined;
|
|
@@ -760,24 +947,122 @@ export class JaakStamps {
|
|
|
760
947
|
}
|
|
761
948
|
}
|
|
762
949
|
}
|
|
950
|
+
async takeManualScreenshot() {
|
|
951
|
+
if (!this.videoRef)
|
|
952
|
+
return;
|
|
953
|
+
// When using manual capture, use mask coordinates for cropping
|
|
954
|
+
await this.takeScreenshotWithMaskCoordinates();
|
|
955
|
+
}
|
|
956
|
+
async takeScreenshotWithMaskCoordinates() {
|
|
957
|
+
if (!this.videoRef || !this.detectionContainer)
|
|
958
|
+
return;
|
|
959
|
+
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
960
|
+
this.triggerCaptureAnimation();
|
|
961
|
+
// Use pooled canvas for optimization
|
|
962
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
963
|
+
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
964
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
965
|
+
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
966
|
+
// Calculate mask coordinates for cropping
|
|
967
|
+
const container = this.detectionContainer.parentElement;
|
|
968
|
+
const containerRect = container.getBoundingClientRect();
|
|
969
|
+
// Get mask dimensions from CSS properties
|
|
970
|
+
const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
|
|
971
|
+
const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
|
|
972
|
+
const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
|
|
973
|
+
const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
|
|
974
|
+
// Convert percentages to actual video coordinates
|
|
975
|
+
const videoWidth = this.videoRef.videoWidth;
|
|
976
|
+
const videoHeight = this.videoRef.videoHeight;
|
|
977
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
978
|
+
const containerAspectRatio = containerRect.width / containerRect.height;
|
|
979
|
+
let displayedVideoWidth, displayedVideoHeight;
|
|
980
|
+
let videoOffsetX = 0, videoOffsetY = 0;
|
|
981
|
+
if (videoAspectRatio > containerAspectRatio) {
|
|
982
|
+
displayedVideoWidth = containerRect.width;
|
|
983
|
+
displayedVideoHeight = containerRect.width / videoAspectRatio;
|
|
984
|
+
videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
displayedVideoHeight = containerRect.height;
|
|
988
|
+
displayedVideoWidth = containerRect.height * videoAspectRatio;
|
|
989
|
+
videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
|
|
990
|
+
}
|
|
991
|
+
// Calculate mask coordinates in video space
|
|
992
|
+
const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
|
|
993
|
+
const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
|
|
994
|
+
const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
|
|
995
|
+
const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
|
|
996
|
+
// Convert to video coordinates
|
|
997
|
+
const scaleX = videoWidth / displayedVideoWidth;
|
|
998
|
+
const scaleY = videoHeight / displayedVideoHeight;
|
|
999
|
+
const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
|
|
1000
|
+
const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
|
|
1001
|
+
const maskWidthInVideo = maskWidthInContainer * scaleX;
|
|
1002
|
+
const maskHeightInVideo = maskHeightInContainer * scaleY;
|
|
1003
|
+
// Calculate crop coordinates
|
|
1004
|
+
const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
|
|
1005
|
+
const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
|
|
1006
|
+
const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
|
|
1007
|
+
const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
|
|
1008
|
+
// Use pooled canvas for cropped version
|
|
1009
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
1010
|
+
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
1011
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
1012
|
+
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
1013
|
+
const captureState = this.stateManager.getCaptureState();
|
|
1014
|
+
if (captureState.step === 'front') {
|
|
1015
|
+
this.stateManager.setCapturedImages({
|
|
1016
|
+
front: {
|
|
1017
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
1018
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
// Check if document classification is enabled (independent of detector)
|
|
1022
|
+
if (this.useDocumentClassification) {
|
|
1023
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
1024
|
+
if (classification && classification.class === 'passport') {
|
|
1025
|
+
this.completeProcess(true);
|
|
1026
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1027
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
this.stateManager.updateCaptureState({
|
|
1032
|
+
step: 'back',
|
|
1033
|
+
isDetectionPaused: true,
|
|
1034
|
+
showFlipAnimation: true
|
|
1035
|
+
});
|
|
1036
|
+
setTimeout(() => {
|
|
1037
|
+
this.stateManager.updateCaptureState({
|
|
1038
|
+
showFlipAnimation: false,
|
|
1039
|
+
isDetectionPaused: false
|
|
1040
|
+
});
|
|
1041
|
+
this.startBackDocumentTimer();
|
|
1042
|
+
}, 3000);
|
|
1043
|
+
}
|
|
1044
|
+
else if (captureState.step === 'back') {
|
|
1045
|
+
this.stateManager.setCapturedImages({
|
|
1046
|
+
back: {
|
|
1047
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
1048
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
this.completeProcess(false);
|
|
1052
|
+
}
|
|
1053
|
+
// Return canvases to pool after use
|
|
1054
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1055
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
1056
|
+
}
|
|
763
1057
|
async takeScreenshot() {
|
|
764
1058
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
765
1059
|
return;
|
|
766
|
-
this.logger.state('INICIANDO_CAPTURA', {
|
|
767
|
-
captureStep: this.stateManager.getCaptureState().step,
|
|
768
|
-
detectedBox: this.lastDetectedBox,
|
|
769
|
-
videoResolution: {
|
|
770
|
-
width: this.videoRef.videoWidth,
|
|
771
|
-
height: this.videoRef.videoHeight
|
|
772
|
-
}
|
|
773
|
-
});
|
|
774
1060
|
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
775
1061
|
this.triggerCaptureAnimation();
|
|
776
|
-
//
|
|
777
|
-
const captureCanvas =
|
|
778
|
-
captureCanvas.width = this.videoRef.videoWidth;
|
|
779
|
-
captureCanvas.height = this.videoRef.videoHeight;
|
|
1062
|
+
// Use pooled canvas for optimization
|
|
1063
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
780
1064
|
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
1065
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
781
1066
|
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
782
1067
|
// Calculate crop coordinates
|
|
783
1068
|
const INPUT_SIZE = 320;
|
|
@@ -787,11 +1072,10 @@ export class JaakStamps {
|
|
|
787
1072
|
const cropY = Math.max(0, (this.lastDetectedBox.y * scaleY) - this.cropMargin);
|
|
788
1073
|
const cropWidth = Math.min((this.lastDetectedBox.w * scaleX) + (2 * this.cropMargin), this.videoRef.videoWidth - cropX);
|
|
789
1074
|
const cropHeight = Math.min((this.lastDetectedBox.h * scaleY) + (2 * this.cropMargin), this.videoRef.videoHeight - cropY);
|
|
790
|
-
//
|
|
791
|
-
const croppedCanvas =
|
|
792
|
-
croppedCanvas.width = cropWidth;
|
|
793
|
-
croppedCanvas.height = cropHeight;
|
|
1075
|
+
// Use pooled canvas for cropped version
|
|
1076
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
794
1077
|
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
1078
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
795
1079
|
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
796
1080
|
const captureState = this.stateManager.getCaptureState();
|
|
797
1081
|
if (captureState.step === 'front') {
|
|
@@ -805,7 +1089,6 @@ export class JaakStamps {
|
|
|
805
1089
|
if (this.useDocumentClassification) {
|
|
806
1090
|
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
807
1091
|
if (classification && classification.class === 'passport') {
|
|
808
|
-
this.logger.state('PASAPORTE_DETECTADO_SALTANDO_REVERSO', { classification: classification?.class });
|
|
809
1092
|
this.completeProcess(true);
|
|
810
1093
|
return;
|
|
811
1094
|
}
|
|
@@ -832,6 +1115,9 @@ export class JaakStamps {
|
|
|
832
1115
|
});
|
|
833
1116
|
this.completeProcess(false);
|
|
834
1117
|
}
|
|
1118
|
+
// Return canvases to pool after use
|
|
1119
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1120
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
835
1121
|
}
|
|
836
1122
|
triggerCaptureAnimation() {
|
|
837
1123
|
const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
|
|
@@ -860,11 +1146,6 @@ export class JaakStamps {
|
|
|
860
1146
|
setTimeout(() => {
|
|
861
1147
|
this.stateManager.updateCaptureState({ showSuccessAnimation: false });
|
|
862
1148
|
}, 3000);
|
|
863
|
-
this.logger.state('PROCESO_COMPLETADO', {
|
|
864
|
-
skippedBack,
|
|
865
|
-
totalImages: capturedImages.metadata.totalImages,
|
|
866
|
-
timestamp: new Date().toISOString()
|
|
867
|
-
});
|
|
868
1149
|
}
|
|
869
1150
|
stopDetection() {
|
|
870
1151
|
if (this.animationId) {
|
|
@@ -873,7 +1154,6 @@ export class JaakStamps {
|
|
|
873
1154
|
}
|
|
874
1155
|
this.clearBackDocumentTimer();
|
|
875
1156
|
this.detectionBoxes = [];
|
|
876
|
-
this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
|
|
877
1157
|
}
|
|
878
1158
|
startBackDocumentTimer() {
|
|
879
1159
|
if (!this.enableBackDocumentTimer)
|
|
@@ -902,6 +1182,15 @@ export class JaakStamps {
|
|
|
902
1182
|
return; // Don't toggle if switching camera
|
|
903
1183
|
this.showCameraSelector = !this.showCameraSelector;
|
|
904
1184
|
}
|
|
1185
|
+
async updateCameraInfoWithAutofocus() {
|
|
1186
|
+
try {
|
|
1187
|
+
const cameraInfo = await this.cameraService.getCameraInfo();
|
|
1188
|
+
this.cameraInfoWithAutofocus = cameraInfo;
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
// Keep existing state if update fails
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
905
1194
|
async handleCameraSwitch(cameraId) {
|
|
906
1195
|
if (this.isSwitchingCamera)
|
|
907
1196
|
return; // Prevent multiple simultaneous switches
|
|
@@ -909,10 +1198,6 @@ export class JaakStamps {
|
|
|
909
1198
|
// Close the selector immediately when user selects a camera
|
|
910
1199
|
this.showCameraSelector = false;
|
|
911
1200
|
this.isSwitchingCamera = true;
|
|
912
|
-
this.logger.state('INICIANDO_CAMBIO_CAMARA', {
|
|
913
|
-
from: this.cameraService.getSelectedCameraId(),
|
|
914
|
-
to: cameraId
|
|
915
|
-
});
|
|
916
1201
|
// Stop current video stream
|
|
917
1202
|
if (this.videoStream) {
|
|
918
1203
|
this.videoStream.getTracks().forEach(track => track.stop());
|
|
@@ -923,21 +1208,17 @@ export class JaakStamps {
|
|
|
923
1208
|
const newStream = await this.cameraService.setupCamera();
|
|
924
1209
|
// Update video element
|
|
925
1210
|
await this.initializeVideoStream(newStream);
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
isRearCamera: this.cameraService.isRearCamera(newStream)
|
|
929
|
-
});
|
|
1211
|
+
// Update camera info with autofocus data
|
|
1212
|
+
await this.updateCameraInfoWithAutofocus();
|
|
930
1213
|
}
|
|
931
1214
|
catch (error) {
|
|
932
|
-
this.logger.error('Error al cambiar cámara:', error);
|
|
933
1215
|
// Try to restore previous camera if switch failed
|
|
934
1216
|
try {
|
|
935
1217
|
const fallbackStream = await this.cameraService.setupCamera();
|
|
936
1218
|
await this.initializeVideoStream(fallbackStream);
|
|
937
1219
|
}
|
|
938
1220
|
catch (fallbackError) {
|
|
939
|
-
this.
|
|
940
|
-
this.updateStatus('Error al cambiar cámara', 'No se pudo completar el cambio de dispositivo', 'error');
|
|
1221
|
+
this.updateStatus('Error al cambiar cámara', 'No se pudo cambiar el dispositivo', 'error');
|
|
941
1222
|
}
|
|
942
1223
|
}
|
|
943
1224
|
finally {
|
|
@@ -978,7 +1259,7 @@ export class JaakStamps {
|
|
|
978
1259
|
this.stateManager.updateCaptureState({ isVideoActive: false, isLoading: false });
|
|
979
1260
|
}
|
|
980
1261
|
this.isMaskReady = false;
|
|
981
|
-
this.updateStatus('
|
|
1262
|
+
this.updateStatus('Captura finalizada', '', 'ready');
|
|
982
1263
|
this.detectionBoxes = [];
|
|
983
1264
|
this.cleanup();
|
|
984
1265
|
}
|
|
@@ -989,7 +1270,6 @@ export class JaakStamps {
|
|
|
989
1270
|
this.performanceUpdateInterval = window.setInterval(() => {
|
|
990
1271
|
this.updatePerformanceMetrics();
|
|
991
1272
|
}, 500);
|
|
992
|
-
this.logger.debug('Monitor de performance inicializado');
|
|
993
1273
|
}
|
|
994
1274
|
updatePerformanceMetrics() {
|
|
995
1275
|
const currentTime = performance.now();
|
|
@@ -1032,6 +1312,66 @@ export class JaakStamps {
|
|
|
1032
1312
|
if (detectionsFound > 0) {
|
|
1033
1313
|
this.performanceMetrics.successfulDetections++;
|
|
1034
1314
|
}
|
|
1315
|
+
// Update performance history for adaptive frame skipping
|
|
1316
|
+
this.performanceHistory.push(processingTime);
|
|
1317
|
+
if (this.performanceHistory.length > this.PERFORMANCE_HISTORY_SIZE) {
|
|
1318
|
+
this.performanceHistory.shift();
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
// Adaptive frame skipping based on performance
|
|
1322
|
+
getAdaptiveFrameSkip() {
|
|
1323
|
+
// Base conditions for high frame skip
|
|
1324
|
+
if (this.consecutiveFailures > 20)
|
|
1325
|
+
return this.MAX_FRAME_SKIP;
|
|
1326
|
+
if (this.performanceMetrics.inferenceTime > 200)
|
|
1327
|
+
return 6;
|
|
1328
|
+
if (this.performanceMetrics.memoryUsage > 200)
|
|
1329
|
+
return 5;
|
|
1330
|
+
// Calculate average processing time
|
|
1331
|
+
if (this.performanceHistory.length > 0) {
|
|
1332
|
+
const avgProcessingTime = this.performanceHistory.reduce((a, b) => a + b, 0) / this.performanceHistory.length;
|
|
1333
|
+
if (avgProcessingTime > 100)
|
|
1334
|
+
return 4;
|
|
1335
|
+
if (avgProcessingTime > 80)
|
|
1336
|
+
return 3;
|
|
1337
|
+
if (avgProcessingTime > 60)
|
|
1338
|
+
return this.BASE_FRAME_SKIP + 1;
|
|
1339
|
+
}
|
|
1340
|
+
// Low memory devices get higher frame skip
|
|
1341
|
+
if (this.performanceMetrics.memoryUsage > 150)
|
|
1342
|
+
return 4;
|
|
1343
|
+
// Performance is good, use base frame skip
|
|
1344
|
+
return this.BASE_FRAME_SKIP;
|
|
1345
|
+
}
|
|
1346
|
+
// Canvas pool management for screenshots
|
|
1347
|
+
getPooledCanvas(width, height) {
|
|
1348
|
+
// Try to find a canvas with matching dimensions
|
|
1349
|
+
const matchingIndex = this.canvasPool.findIndex(canvas => canvas.width === width && canvas.height === height);
|
|
1350
|
+
if (matchingIndex !== -1) {
|
|
1351
|
+
return this.canvasPool.splice(matchingIndex, 1)[0];
|
|
1352
|
+
}
|
|
1353
|
+
// If no matching canvas, try to reuse any canvas and resize
|
|
1354
|
+
if (this.canvasPool.length > 0) {
|
|
1355
|
+
const canvas = this.canvasPool.pop();
|
|
1356
|
+
canvas.width = width;
|
|
1357
|
+
canvas.height = height;
|
|
1358
|
+
return canvas;
|
|
1359
|
+
}
|
|
1360
|
+
// Create new canvas if pool is empty
|
|
1361
|
+
const canvas = document.createElement('canvas');
|
|
1362
|
+
canvas.width = width;
|
|
1363
|
+
canvas.height = height;
|
|
1364
|
+
return canvas;
|
|
1365
|
+
}
|
|
1366
|
+
returnCanvasToPool(canvas) {
|
|
1367
|
+
// Only return to pool if not at max capacity
|
|
1368
|
+
if (this.canvasPool.length < this.MAX_CANVAS_POOL_SIZE) {
|
|
1369
|
+
// Clear the canvas before returning to pool
|
|
1370
|
+
const ctx = canvas.getContext('2d');
|
|
1371
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1372
|
+
this.canvasPool.push(canvas);
|
|
1373
|
+
}
|
|
1374
|
+
// If pool is full, let the canvas be garbage collected
|
|
1035
1375
|
}
|
|
1036
1376
|
static get is() { return "jaak-stamps"; }
|
|
1037
1377
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1147,6 +1487,26 @@ export class JaakStamps {
|
|
|
1147
1487
|
"reflect": false,
|
|
1148
1488
|
"defaultValue": "false"
|
|
1149
1489
|
},
|
|
1490
|
+
"useDocumentDetector": {
|
|
1491
|
+
"type": "boolean",
|
|
1492
|
+
"attribute": "use-document-detector",
|
|
1493
|
+
"mutable": false,
|
|
1494
|
+
"complexType": {
|
|
1495
|
+
"original": "boolean",
|
|
1496
|
+
"resolved": "boolean",
|
|
1497
|
+
"references": {}
|
|
1498
|
+
},
|
|
1499
|
+
"required": false,
|
|
1500
|
+
"optional": false,
|
|
1501
|
+
"docs": {
|
|
1502
|
+
"tags": [],
|
|
1503
|
+
"text": ""
|
|
1504
|
+
},
|
|
1505
|
+
"getter": false,
|
|
1506
|
+
"setter": false,
|
|
1507
|
+
"reflect": false,
|
|
1508
|
+
"defaultValue": "true"
|
|
1509
|
+
},
|
|
1150
1510
|
"preferredCamera": {
|
|
1151
1511
|
"type": "string",
|
|
1152
1512
|
"attribute": "preferred-camera",
|
|
@@ -1238,9 +1598,11 @@ export class JaakStamps {
|
|
|
1238
1598
|
"showCameraSelector": {},
|
|
1239
1599
|
"isSwitchingCamera": {},
|
|
1240
1600
|
"hasDocumentDetected": {},
|
|
1601
|
+
"cameraInfoWithAutofocus": {},
|
|
1241
1602
|
"currentStatus": {},
|
|
1242
1603
|
"performanceData": {},
|
|
1243
|
-
"backDocumentTimerRemaining": {}
|
|
1604
|
+
"backDocumentTimerRemaining": {},
|
|
1605
|
+
"showManualCaptureButton": {}
|
|
1244
1606
|
};
|
|
1245
1607
|
}
|
|
1246
1608
|
static get events() {
|
|
@@ -1319,7 +1681,7 @@ export class JaakStamps {
|
|
|
1319
1681
|
},
|
|
1320
1682
|
"startCapture": {
|
|
1321
1683
|
"complexType": {
|
|
1322
|
-
"signature": "() => Promise<
|
|
1684
|
+
"signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
|
|
1323
1685
|
"parameters": [],
|
|
1324
1686
|
"references": {
|
|
1325
1687
|
"Promise": {
|
|
@@ -1327,7 +1689,7 @@ export class JaakStamps {
|
|
|
1327
1689
|
"id": "global::Promise"
|
|
1328
1690
|
}
|
|
1329
1691
|
},
|
|
1330
|
-
"return": "Promise<
|
|
1692
|
+
"return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
|
|
1331
1693
|
},
|
|
1332
1694
|
"docs": {
|
|
1333
1695
|
"text": "",
|
|
@@ -1336,7 +1698,7 @@ export class JaakStamps {
|
|
|
1336
1698
|
},
|
|
1337
1699
|
"stopCapture": {
|
|
1338
1700
|
"complexType": {
|
|
1339
|
-
"signature": "() => Promise<
|
|
1701
|
+
"signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
|
|
1340
1702
|
"parameters": [],
|
|
1341
1703
|
"references": {
|
|
1342
1704
|
"Promise": {
|
|
@@ -1344,7 +1706,7 @@ export class JaakStamps {
|
|
|
1344
1706
|
"id": "global::Promise"
|
|
1345
1707
|
}
|
|
1346
1708
|
},
|
|
1347
|
-
"return": "Promise<
|
|
1709
|
+
"return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
|
|
1348
1710
|
},
|
|
1349
1711
|
"docs": {
|
|
1350
1712
|
"text": "",
|
|
@@ -1353,7 +1715,7 @@ export class JaakStamps {
|
|
|
1353
1715
|
},
|
|
1354
1716
|
"resetCapture": {
|
|
1355
1717
|
"complexType": {
|
|
1356
|
-
"signature": "() => Promise<
|
|
1718
|
+
"signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
|
|
1357
1719
|
"parameters": [],
|
|
1358
1720
|
"references": {
|
|
1359
1721
|
"Promise": {
|
|
@@ -1361,7 +1723,7 @@ export class JaakStamps {
|
|
|
1361
1723
|
"id": "global::Promise"
|
|
1362
1724
|
}
|
|
1363
1725
|
},
|
|
1364
|
-
"return": "Promise<
|
|
1726
|
+
"return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
|
|
1365
1727
|
},
|
|
1366
1728
|
"docs": {
|
|
1367
1729
|
"text": "",
|
|
@@ -1370,7 +1732,7 @@ export class JaakStamps {
|
|
|
1370
1732
|
},
|
|
1371
1733
|
"skipBackCapture": {
|
|
1372
1734
|
"complexType": {
|
|
1373
|
-
"signature": "() => Promise<
|
|
1735
|
+
"signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
|
|
1374
1736
|
"parameters": [],
|
|
1375
1737
|
"references": {
|
|
1376
1738
|
"Promise": {
|
|
@@ -1378,7 +1740,7 @@ export class JaakStamps {
|
|
|
1378
1740
|
"id": "global::Promise"
|
|
1379
1741
|
}
|
|
1380
1742
|
},
|
|
1381
|
-
"return": "Promise<
|
|
1743
|
+
"return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
|
|
1382
1744
|
},
|
|
1383
1745
|
"docs": {
|
|
1384
1746
|
"text": "",
|
|
@@ -1448,7 +1810,7 @@ export class JaakStamps {
|
|
|
1448
1810
|
},
|
|
1449
1811
|
"setPreferredCamera": {
|
|
1450
1812
|
"complexType": {
|
|
1451
|
-
"signature": "(camera: \"auto\" | \"front\" | \"back\") => Promise<{ success: boolean; selectedCamera: string; availableCameras: number; }>",
|
|
1813
|
+
"signature": "(camera: \"auto\" | \"front\" | \"back\") => Promise<{ success: boolean; selectedCamera: string; availableCameras: number; error?: undefined; } | { success: boolean; error: any; selectedCamera: any; availableCameras: number; }>",
|
|
1452
1814
|
"parameters": [{
|
|
1453
1815
|
"name": "camera",
|
|
1454
1816
|
"type": "\"auto\" | \"front\" | \"back\"",
|
|
@@ -1460,7 +1822,7 @@ export class JaakStamps {
|
|
|
1460
1822
|
"id": "global::Promise"
|
|
1461
1823
|
}
|
|
1462
1824
|
},
|
|
1463
|
-
"return": "Promise<{ success: boolean; selectedCamera: string; availableCameras: number; }>"
|
|
1825
|
+
"return": "Promise<{ success: boolean; selectedCamera: string; availableCameras: number; error?: undefined; } | { success: boolean; error: any; selectedCamera: any; availableCameras: number; }>"
|
|
1464
1826
|
},
|
|
1465
1827
|
"docs": {
|
|
1466
1828
|
"text": "",
|
|
@@ -1469,7 +1831,7 @@ export class JaakStamps {
|
|
|
1469
1831
|
},
|
|
1470
1832
|
"setCaptureDelay": {
|
|
1471
1833
|
"complexType": {
|
|
1472
|
-
"signature": "(delay: number) => Promise<{ success: boolean; captureDelay: number; }>",
|
|
1834
|
+
"signature": "(delay: number) => Promise<{ success: boolean; error: string; captureDelay: number; } | { success: boolean; captureDelay: number; error?: undefined; }>",
|
|
1473
1835
|
"parameters": [{
|
|
1474
1836
|
"name": "delay",
|
|
1475
1837
|
"type": "number",
|
|
@@ -1481,7 +1843,7 @@ export class JaakStamps {
|
|
|
1481
1843
|
"id": "global::Promise"
|
|
1482
1844
|
}
|
|
1483
1845
|
},
|
|
1484
|
-
"return": "Promise<{ success: boolean; captureDelay: number; }>"
|
|
1846
|
+
"return": "Promise<{ success: boolean; error: string; captureDelay: number; } | { success: boolean; captureDelay: number; error?: undefined; }>"
|
|
1485
1847
|
},
|
|
1486
1848
|
"docs": {
|
|
1487
1849
|
"text": "",
|
|
@@ -1504,52 +1866,6 @@ export class JaakStamps {
|
|
|
1504
1866
|
"text": "",
|
|
1505
1867
|
"tags": []
|
|
1506
1868
|
}
|
|
1507
|
-
},
|
|
1508
|
-
"setTorchEnabled": {
|
|
1509
|
-
"complexType": {
|
|
1510
|
-
"signature": "(enabled: boolean) => Promise<{ success: boolean; enabled: boolean; }>",
|
|
1511
|
-
"parameters": [{
|
|
1512
|
-
"name": "enabled",
|
|
1513
|
-
"type": "boolean",
|
|
1514
|
-
"docs": ""
|
|
1515
|
-
}],
|
|
1516
|
-
"references": {
|
|
1517
|
-
"Promise": {
|
|
1518
|
-
"location": "global",
|
|
1519
|
-
"id": "global::Promise"
|
|
1520
|
-
}
|
|
1521
|
-
},
|
|
1522
|
-
"return": "Promise<{ success: boolean; enabled: boolean; }>"
|
|
1523
|
-
},
|
|
1524
|
-
"docs": {
|
|
1525
|
-
"text": "",
|
|
1526
|
-
"tags": []
|
|
1527
|
-
}
|
|
1528
|
-
},
|
|
1529
|
-
"focusAtPoint": {
|
|
1530
|
-
"complexType": {
|
|
1531
|
-
"signature": "(x: number, y: number) => Promise<{ success: boolean; coordinates: { x: number; y: number; }; }>",
|
|
1532
|
-
"parameters": [{
|
|
1533
|
-
"name": "x",
|
|
1534
|
-
"type": "number",
|
|
1535
|
-
"docs": ""
|
|
1536
|
-
}, {
|
|
1537
|
-
"name": "y",
|
|
1538
|
-
"type": "number",
|
|
1539
|
-
"docs": ""
|
|
1540
|
-
}],
|
|
1541
|
-
"references": {
|
|
1542
|
-
"Promise": {
|
|
1543
|
-
"location": "global",
|
|
1544
|
-
"id": "global::Promise"
|
|
1545
|
-
}
|
|
1546
|
-
},
|
|
1547
|
-
"return": "Promise<{ success: boolean; coordinates: { x: number; y: number; }; }>"
|
|
1548
|
-
},
|
|
1549
|
-
"docs": {
|
|
1550
|
-
"text": "",
|
|
1551
|
-
"tags": []
|
|
1552
|
-
}
|
|
1553
1869
|
}
|
|
1554
1870
|
};
|
|
1555
1871
|
}
|