@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.
Files changed (45) hide show
  1. package/README.md +11 -1
  2. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  3. package/dist/cjs/jaak-stamps.cjs.entry.js +759 -623
  4. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  5. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  6. package/dist/cjs/loader.cjs.js +1 -1
  7. package/dist/collection/components/my-component/my-component.css +178 -11
  8. package/dist/collection/components/my-component/my-component.js +566 -250
  9. package/dist/collection/components/my-component/my-component.js.map +1 -1
  10. package/dist/collection/services/CameraService.js +156 -344
  11. package/dist/collection/services/CameraService.js.map +1 -1
  12. package/dist/collection/services/DetectionService.js +68 -38
  13. package/dist/collection/services/DetectionService.js.map +1 -1
  14. package/dist/collection/services/EventBusService.js +1 -1
  15. package/dist/collection/services/EventBusService.js.map +1 -1
  16. package/dist/collection/services/ServiceContainer.js +4 -13
  17. package/dist/collection/services/ServiceContainer.js.map +1 -1
  18. package/dist/collection/services/interfaces/ICameraService.js.map +1 -1
  19. package/dist/collection/types/component-types.js.map +1 -1
  20. package/dist/components/jaak-stamps.js +763 -626
  21. package/dist/components/jaak-stamps.js.map +1 -1
  22. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  23. package/dist/esm/jaak-stamps.entry.js +759 -623
  24. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  25. package/dist/esm/loader.js +1 -1
  26. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  27. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  28. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js +2 -0
  29. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js.map +1 -0
  30. package/dist/types/components/my-component/my-component.d.ts +68 -17
  31. package/dist/types/components.d.ts +14 -8
  32. package/dist/types/services/CameraService.d.ts +12 -14
  33. package/dist/types/services/DetectionService.d.ts +10 -3
  34. package/dist/types/services/ServiceContainer.d.ts +1 -2
  35. package/dist/types/services/interfaces/ICameraService.d.ts +3 -12
  36. package/dist/types/types/component-types.d.ts +3 -0
  37. package/package.json +2 -2
  38. package/dist/collection/services/LoggerService.js +0 -40
  39. package/dist/collection/services/LoggerService.js.map +0 -1
  40. package/dist/collection/services/interfaces/ILogger.js +0 -2
  41. package/dist/collection/services/interfaces/ILogger.js.map +0 -1
  42. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js +0 -2
  43. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js.map +0 -1
  44. package/dist/types/services/LoggerService.d.ts +0 -12
  45. 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: 'Inicializando componente...',
28
- description: 'Configurando servicios y cargando recursos',
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
- FRAME_SKIP = 2;
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('Iniciando servicios...', 'Configurando módulos internos', 'initializing');
98
+ this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
85
99
  await this.initializeServices();
86
- this.updateStatus('Configurando eventos...', 'Preparando comunicación entre servicios', 'initializing');
100
+ this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
87
101
  await this.setupEventListeners();
88
- this.updateStatus('Inicializando cámara...', 'Detectando dispositivos disponibles', 'initializing');
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', (cameraId) => {
117
- this.logger.state('CAMARA_CAMBIADA_EVENT', { cameraId });
129
+ this.eventBus.on('camera-changed', () => {
130
+ // Camera changed event
118
131
  });
119
- this.eventBus.on('error', (error) => {
120
- this.logger.error('Error en servicio:', error);
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.updateStatus('Cargando modelo IA...', 'Preparando reconocimiento de documentos', 'initializing');
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('Descargando librerías...', 'Obteniendo recursos de reconocimiento', 'initializing');
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
- this.currentStatus = {
187
- message: 'Listo para capturar',
188
- description: '',
189
- type: 'ready',
190
- isInitialized: true
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
- this.logger.state('COMPONENTE_LISTO', {
206
- ortLibraryLoaded: !!window.ort,
207
- modelPreloaded: this.detectionService.isModelLoaded(),
208
- isReady: isDocumentReady
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
- await this.startDetection();
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.exitSession();
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.resetDetection();
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 captureState = this.stateManager.getCaptureState();
316
- const capturedImages = this.stateManager.getCapturedImages();
317
- if (captureState.step === 'back' && capturedImages.front.fullFrame) {
318
- this.completeProcess(true);
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 captureState = this.stateManager.getCaptureState();
323
- const capturedImages = this.stateManager.getCapturedImages();
324
- return {
325
- isVideoActive: captureState.isVideoActive,
326
- captureStep: captureState.step,
327
- hasImages: !!(capturedImages.front.fullFrame || capturedImages.back.fullFrame),
328
- isProcessCompleted: this.stateManager.isProcessCompleted(),
329
- isModelPreloaded: this.detectionService.isModelLoaded()
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.logger.state('MODELO_YA_PRECARGADO');
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('Descargando modelo de detección...', 'Obteniendo red neuronal para reconocimiento de documentos', 'loading');
341
- this.stateManager.updateCaptureState({ isLoading: true });
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('Cargando clasificador...', 'Preparando modelo de clasificación de tipos de documento', 'loading');
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('Optimizando modelos...', 'Configurando parámetros de rendimiento', 'loading');
505
+ this.updateStatus('Finalizando configuración...', 'Preparando herramientas para captura', 'loading');
352
506
  await new Promise(resolve => setTimeout(resolve, 300));
353
- this.updateStatus('Modelos precargados', '', 'ready');
354
- this.stateManager.updateCaptureState({ isLoading: false });
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.logger.error('Error al precargar modelos:', error);
361
- this.updateStatus('Error al cargar modelos', 'No se pudieron descargar los recursos necesarios', 'error');
362
- this.stateManager.updateCaptureState({ isLoading: false });
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
- return this.cameraService.getCameraInfo();
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
- this.preferredCamera = camera;
371
- this.serviceContainer.updateConfig({ preferredCamera: camera });
372
- await this.cameraService.enumerateDevices();
373
- const captureState = this.stateManager.getCaptureState();
374
- if (captureState.isVideoActive) {
375
- const selectedCameraId = this.cameraService.getSelectedCameraId();
376
- if (selectedCameraId) {
377
- await this.cameraService.switchCamera(selectedCameraId);
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
- throw new Error('Capture delay must be between 0 and 10000 milliseconds');
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.updateConfig({ captureDelay: delay });
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('Cargando modelo de detección...', 'Descargando red neuronal para reconocimiento', 'loading');
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('Cargando clasificador...', 'Preparando modelo de clasificación de documentos', 'loading');
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('Detectando cámaras...', 'Buscando dispositivos de captura disponibles', 'loading');
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('Configurando cámara...', 'Estableciendo resolución y parámetros óptimos', 'loading');
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.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
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.logger.error('Error al inicializar detección:', err);
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
- // Frame skipping for performance
702
+ // Adaptive frame skipping for performance
495
703
  this.frameSkipCounter++;
496
- if (this.frameSkipCounter <= this.FRAME_SKIP) {
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.cameraService?.getCameraInfo() || {
614
- availableCameras: [],
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: 'c2596f8cabb227e3ea3b082495f52b141cb1690f', class: "overlay-mask" }, h("div", { key: 'ba1ea39b5df1cd312a9bd9410b7225a29cfb8a29', class: "card-outline" }, h("div", { key: '6ce4d4d969d5f3fbcf75a4eb285f61d434c5633b', class: "side side-top" }), h("div", { key: '36653ecc4fcda25dfc84bc4d1f4c4c5dfb71ced7', class: "side side-right" }), h("div", { key: '8b108fa8fb370b229a0d1a77cb1ff044319a8f75', class: "side side-bottom" }), h("div", { key: 'a7aa3b45c065a50445aafd5c3ccefb57b0c9c769', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'b94b92273e12a019238e11ae57f21e140f13c3f1', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '144c3741003ec0cc148bb6ce92012d7b3cd110c8', class: "skip-section" }, h("div", { key: 'f0a984a6b75afa374c5d47da9d53e39f98071da0', class: "skip-explanation" }, "Si tu documento no tiene lado trasero, da clic en el bot\u00F3n para continuar con el proceso"), h("button", { key: '57ac30d776e30709aab6e0a621cef889da3cc677', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
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: 'bd45628707e0169317ed3dd4247e1056d7046104', class: "camera-controls" }, h("button", { key: 'e750fbf4d5d76d9e9596823bb2b86801ef19e485', 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: '40ada780a8ea13aeeaee9cb39bc6496f69f2679d', class: "camera-selector-dropdown" }, h("div", { key: '3a920bb02ba024f0359513f10544bf0528ecee6d', class: "camera-selector-header" }, h("span", { key: 'e5efd478a602b13821ba53cb9a7a59d1b4a71178' }, "Seleccionar C\u00E1mara"), h("button", { key: '2df6cc11e162be74862029e33815927e3fa8f2f8', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '06934642dff134c9e1a6872f935a5892a9c7b835', 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}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '8d5ede287c2efb0ebe418bee8dc7fb310443c991', class: "device-info" }, h("small", { key: 'b7dd10565113694b09f01ab28ffa29262ae1d2bc' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '2f9e549e6c0c56d4db5835e58361fb17bba6cf90', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'a6ef0ed127f23e9d764896ed743ca8d8454851a6', class: "flip-animation" }, h("div", { key: '0b04411f88a634bb3243b00ca2d5a45fd6962dea', class: "id-card-icon" }), h("div", { key: '9327be58879b407cea87f455989406e58302e9d1', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'bd268578b5003e99256e3340b4a9a6fb7656be64', class: "success-animation" }, h("div", { key: '064e0a5b9851de14bc6829f028e7ea7161fd1633', class: "check-icon" }), h("div", { key: 'bdcd94a8806399e396e218a359c10b11d05f38d6', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '3d9af617376c16e44e065873919c3d90ff46110a', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'be7b56decfd6f12dcca46b2a9456480e3d5ac00e', class: "status-spinner" })), h("div", { key: '5fe9850883cb749cebaa18c3e9c13ca21325e373', class: "status-content" }, h("div", { key: '43fdb50691e4b98e56126af1a129f625061128bf', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'b76cbfa4a0349421fcd535d3fd8bf40c89ca3aef', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'eddefb464af0487ab9ab2a0f76dccbb6a2092e51', class: "performance-monitor" }, h("div", { key: '7b7d93b56cb4cd4fbb1bdde89c6150b282fa805e', class: "performance-expanded" }, h("div", { key: 'f302e8b9d985fb8cad53c4c4e0d37a3bf5a63261', class: "metrics-row" }, h("div", { key: 'e8d8f2420ee8643a7f454d38065c75b5be598f77', class: "metric-compact" }, h("span", { key: '6e52eb5bb06e0df57e61774de38c31349f45e22b', class: "metric-label" }, "FPS"), h("span", { key: '6636f9cea20c49b49c17db86188e2f6b214c45c3', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'afa1d9dec05b14256a1b1fcec8638b8c21a56e37', class: "metric-compact" }, h("span", { key: '9c55e30f5d01582648bacc1fb38ca1990485a591', class: "metric-label" }, "MEM"), h("span", { key: 'ca263601e8715027f95ffb2d5413a643dc570cf2', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '03caf19961fd01b7781484d49d8121b1e78c161f', class: "metrics-row" }, h("div", { key: 'bffb08f01f703411f7c8605e4f8d95b1c37b5c0f', class: "metric-compact" }, h("span", { key: '9b92279b3253cc669412733facb3751f4344d69f', class: "metric-label" }, "INF"), h("span", { key: '3234d4150e2e1479e3b552767af8123e69911d6c', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '77082d2669db53f19fb50b3bbfa1ebefb0b86ff4', class: "metric-compact" }, h("span", { key: 'cc6ef96c72c800ba36dab2a388f1de863b2dacd5', class: "metric-label" }, "FRAME"), h("span", { key: 'e2bb4b7351869a76e810f8bbf20d805154495e35', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '5700d7ef839fa77686299d79445203090dc8079f', class: "metrics-row" }, h("div", { key: 'a2999a035fccb7f250c01b084075490678513c87', class: "metric-compact" }, h("span", { key: '3677d58665996fa5969507dbaff9e956b2ea54ee', class: "metric-label" }, "DET"), h("span", { key: 'c584312438b339c82a3bdc33f00fb985ff2a6f55', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '58866219f213bf8607083f30be2d5ca5d0515cea', class: "metric-compact" }, h("span", { key: 'd90b450051331347f219b6c0f197536d0dcee9b7', class: "metric-label" }, "RATE"), h("span", { key: 'f6e6cd7948e06014e5a0416e4e81394fa36ec61d', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: 'c1d0194f181a0082cc1aa5452de382908da61b65', class: "watermark" }, h("img", { key: 'e04d35f3c79a2b4a3922ab5b0f102220171fa7c0', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
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(error => {
740
- this.logger.error('Error al tomar captura de pantalla:', error);
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
- // Create capture canvas
777
- const captureCanvas = document.createElement('canvas');
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
- // Create cropped version
791
- const croppedCanvas = document.createElement('canvas');
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
- this.logger.state('CAMBIO_CAMARA_EXITOSO', {
927
- newCameraId: cameraId,
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.logger.error('Error al restaurar cámara anterior:', fallbackError);
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('Sesión finalizada', '', 'ready');
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<void>",
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<void>"
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<void>",
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<void>"
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<void>",
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<void>"
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<void>",
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<void>"
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
  }