@jaak.ai/stamps 2.0.1 → 2.1.0-beta.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/app-globals-V2Kpy_OQ.js +8 -0
- package/dist/cjs/app-globals-V2Kpy_OQ.js.map +1 -0
- package/dist/cjs/{index-BfhtOB0D.js → index-Ga0t6BMe.js} +176 -119
- package/dist/cjs/index-Ga0t6BMe.js.map +1 -0
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +5 -4
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js.map +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +1273 -663
- 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 +4 -3
- package/dist/cjs/loader.cjs.js.map +1 -1
- package/dist/collection/collection-manifest.json +1 -1
- package/dist/collection/components/my-component/my-component.css +225 -14
- package/dist/collection/components/my-component/my-component.js +964 -286
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/CameraService.js +236 -342
- package/dist/collection/services/CameraService.js.map +1 -1
- package/dist/collection/services/DetectionService.js +100 -42
- 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/index.js +165 -113
- package/dist/components/index.js.map +1 -1
- package/dist/components/jaak-stamps.js +1281 -666
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/app-globals-DQuL1Twl.js +6 -0
- package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
- package/dist/esm/{index-BP1Q4KOg.js → index-Drbzcuq-.js} +177 -119
- package/dist/esm/index-Drbzcuq-.js.map +1 -0
- package/dist/esm/jaak-stamps-webcomponent.js +5 -4
- package/dist/esm/jaak-stamps-webcomponent.js.map +1 -1
- package/dist/esm/jaak-stamps.entry.js +1273 -663
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +4 -3
- package/dist/esm/loader.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/loader.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js.map +1 -0
- package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js +3 -0
- package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js.map +1 -0
- package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +87 -17
- package/dist/types/components.d.ts +14 -8
- package/dist/types/services/CameraService.d.ts +13 -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/stencil-public-runtime.d.ts +12 -5
- package/dist/types/types/component-types.d.ts +3 -0
- package/package.json +6 -4
- package/dist/cjs/index-BfhtOB0D.js.map +0 -1
- 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/esm/index-BP1Q4KOg.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/jaak-stamps-webcomponent/p-BP1Q4KOg.js +0 -3
- package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.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,13 @@ export class JaakStamps {
|
|
|
40
48
|
detectionRate: 0
|
|
41
49
|
};
|
|
42
50
|
backDocumentTimerRemaining = 0;
|
|
51
|
+
showManualCaptureButton = false;
|
|
52
|
+
performanceDegradedMode = false; // Auto-switched to manual mode due to performance
|
|
53
|
+
showPerformanceMessage = false; // Show performance message temporarily
|
|
54
|
+
captureStateVersion = 0; // Triggers re-render when StateManager changes
|
|
55
|
+
processingButton = null; // Track which button is processing
|
|
43
56
|
// Services
|
|
44
57
|
serviceContainer;
|
|
45
|
-
logger;
|
|
46
58
|
eventBus;
|
|
47
59
|
stateManager;
|
|
48
60
|
cameraService;
|
|
@@ -59,12 +71,12 @@ export class JaakStamps {
|
|
|
59
71
|
alignmentStartTime;
|
|
60
72
|
alignmentTimer;
|
|
61
73
|
backDocumentTimer;
|
|
74
|
+
orientationTimer;
|
|
62
75
|
// Performance monitoring
|
|
63
76
|
performanceMetrics = {
|
|
64
77
|
fps: 0,
|
|
65
78
|
inferenceTime: 0,
|
|
66
79
|
memoryUsage: 0,
|
|
67
|
-
cpuUsage: 0,
|
|
68
80
|
onnxLoadTime: 0,
|
|
69
81
|
frameProcessingTime: 0,
|
|
70
82
|
totalDetections: 0,
|
|
@@ -73,22 +85,39 @@ export class JaakStamps {
|
|
|
73
85
|
lastUpdateTime: 0
|
|
74
86
|
};
|
|
75
87
|
performanceUpdateInterval;
|
|
76
|
-
// Performance optimization
|
|
88
|
+
// Performance optimization with adaptive frame skipping
|
|
77
89
|
frameSkipCounter = 0;
|
|
78
|
-
|
|
90
|
+
BASE_FRAME_SKIP = 2;
|
|
91
|
+
MAX_FRAME_SKIP = 8;
|
|
79
92
|
consecutiveFailures = 0;
|
|
80
93
|
MAX_FAILURES = 30;
|
|
81
94
|
lastInferenceTime = 0;
|
|
82
95
|
MIN_INFERENCE_INTERVAL = 50;
|
|
96
|
+
performanceHistory = [];
|
|
97
|
+
PERFORMANCE_HISTORY_SIZE = 10;
|
|
98
|
+
// Sistema simplificado de monitoreo de rendimiento
|
|
99
|
+
PERFORMANCE_THRESHOLD_MS = 2000; // Umbral único para todos los frames
|
|
100
|
+
SLOW_FRAMES_TO_TRIGGER = 2; // Cuántos frames lentos consecutivos antes de cambiar
|
|
101
|
+
slowFrameCount = 0; // Contador de frames consecutivos lentos
|
|
102
|
+
processedFramesCount = 0; // Contador total de frames procesados
|
|
103
|
+
hasAutoSwitchedToManual = false;
|
|
104
|
+
// Canvas pool for optimized screenshot capture
|
|
105
|
+
canvasPool = [];
|
|
106
|
+
MAX_CANVAS_POOL_SIZE = 3;
|
|
83
107
|
async componentDidLoad() {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.
|
|
108
|
+
try {
|
|
109
|
+
this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
|
|
110
|
+
await this.initializeServices();
|
|
111
|
+
this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
|
|
112
|
+
await this.setupEventListeners();
|
|
113
|
+
this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
|
|
114
|
+
await this.initializeComponent();
|
|
115
|
+
if (this.debug) {
|
|
116
|
+
this.initializePerformanceMonitor();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.updateStatus('Error de inicialización', error.message, 'error');
|
|
92
121
|
}
|
|
93
122
|
}
|
|
94
123
|
async initializeServices() {
|
|
@@ -98,37 +127,22 @@ export class JaakStamps {
|
|
|
98
127
|
maskSize: this.maskSize,
|
|
99
128
|
cropMargin: this.cropMargin,
|
|
100
129
|
useDocumentClassification: this.useDocumentClassification,
|
|
130
|
+
useDocumentDetector: this.useDocumentDetector,
|
|
101
131
|
preferredCamera: this.preferredCamera,
|
|
102
132
|
captureDelay: this.captureDelay
|
|
103
133
|
};
|
|
104
134
|
this.serviceContainer = new ServiceContainer(config);
|
|
105
|
-
this.logger = this.serviceContainer.getLogger();
|
|
106
135
|
this.eventBus = this.serviceContainer.getEventBus();
|
|
107
136
|
this.stateManager = this.serviceContainer.getStateManager();
|
|
108
137
|
this.cameraService = this.serviceContainer.getCameraService();
|
|
109
138
|
this.detectionService = this.serviceContainer.getDetectionService();
|
|
110
|
-
this.logger.state('SERVICIOS_INICIALIZADOS', { timestamp: Date.now() });
|
|
111
139
|
}
|
|
112
140
|
async setupEventListeners() {
|
|
113
|
-
this.eventBus.on('state-changed', (
|
|
114
|
-
this.handleStateChange(
|
|
115
|
-
});
|
|
116
|
-
this.eventBus.on('camera-changed', (cameraId) => {
|
|
117
|
-
this.logger.state('CAMARA_CAMBIADA_EVENT', { cameraId });
|
|
118
|
-
});
|
|
119
|
-
this.eventBus.on('error', (error) => {
|
|
120
|
-
this.logger.error('Error en servicio:', error);
|
|
141
|
+
this.eventBus.on('state-changed', () => {
|
|
142
|
+
this.handleStateChange();
|
|
121
143
|
});
|
|
122
144
|
}
|
|
123
145
|
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
146
|
this.validateProps();
|
|
133
147
|
if (this.debug) {
|
|
134
148
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
@@ -137,58 +151,67 @@ export class JaakStamps {
|
|
|
137
151
|
this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura', 'initializing');
|
|
138
152
|
await this.cameraService.detectDeviceType();
|
|
139
153
|
await this.cameraService.enumerateDevices();
|
|
140
|
-
this.
|
|
154
|
+
await this.updateCameraInfoWithAutofocus();
|
|
155
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'initializing');
|
|
141
156
|
await this.loadOnnxRuntime();
|
|
142
157
|
this.initializeResizeObserver();
|
|
143
158
|
}
|
|
144
159
|
validateProps() {
|
|
145
160
|
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
161
|
this.maskSize = 90;
|
|
148
162
|
}
|
|
149
163
|
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
164
|
this.cropMargin = 0;
|
|
152
165
|
}
|
|
153
166
|
const validOptions = ['auto', 'front', 'back'];
|
|
154
167
|
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
168
|
this.preferredCamera = 'auto';
|
|
157
169
|
}
|
|
158
170
|
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
171
|
this.captureDelay = 1500;
|
|
161
172
|
}
|
|
162
173
|
}
|
|
163
174
|
async loadOnnxRuntime() {
|
|
164
175
|
if (!window.ort) {
|
|
165
|
-
this.updateStatus('
|
|
176
|
+
this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
|
|
166
177
|
const script = document.createElement('script');
|
|
167
|
-
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
|
|
178
|
+
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/ort.min.js';
|
|
168
179
|
document.head.appendChild(script);
|
|
169
180
|
await new Promise((resolve) => {
|
|
170
181
|
script.onload = () => {
|
|
171
|
-
setTimeout(() => {
|
|
172
|
-
this.finalizeInitialization();
|
|
182
|
+
setTimeout(async () => {
|
|
183
|
+
await this.finalizeInitialization();
|
|
173
184
|
resolve(undefined);
|
|
174
185
|
}, 300);
|
|
175
186
|
};
|
|
176
187
|
});
|
|
177
188
|
}
|
|
178
189
|
else {
|
|
179
|
-
setTimeout(() => {
|
|
180
|
-
this.finalizeInitialization();
|
|
190
|
+
setTimeout(async () => {
|
|
191
|
+
await this.finalizeInitialization();
|
|
181
192
|
}, 300);
|
|
182
193
|
}
|
|
183
194
|
}
|
|
184
|
-
finalizeInitialization() {
|
|
195
|
+
async finalizeInitialization() {
|
|
185
196
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
// Check camera permissions before showing "ready" status
|
|
198
|
+
const hasPermissions = await this.checkCameraPermissions();
|
|
199
|
+
if (hasPermissions) {
|
|
200
|
+
this.currentStatus = {
|
|
201
|
+
message: 'Listo para capturar',
|
|
202
|
+
description: '',
|
|
203
|
+
type: 'ready',
|
|
204
|
+
isInitialized: true
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
this.currentStatus = {
|
|
209
|
+
message: 'Permisos de cámara requeridos',
|
|
210
|
+
description: 'Por favor, otorgue permisos de cámara para continuar',
|
|
211
|
+
type: 'error',
|
|
212
|
+
isInitialized: true
|
|
213
|
+
};
|
|
214
|
+
}
|
|
192
215
|
this.emitReadyEvent();
|
|
193
216
|
}
|
|
194
217
|
updateStatus(message, description, type = 'loading') {
|
|
@@ -202,20 +225,30 @@ export class JaakStamps {
|
|
|
202
225
|
emitReadyEvent() {
|
|
203
226
|
const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
|
|
204
227
|
this.isReady.emit(isDocumentReady);
|
|
205
|
-
this.logger.state('COMPONENTE_LISTO', {
|
|
206
|
-
ortLibraryLoaded: !!window.ort,
|
|
207
|
-
modelPreloaded: this.detectionService.isModelLoaded(),
|
|
208
|
-
isReady: isDocumentReady
|
|
209
|
-
});
|
|
210
228
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
229
|
+
async checkCameraPermissions() {
|
|
230
|
+
try {
|
|
231
|
+
if (!navigator.permissions) {
|
|
232
|
+
// Fallback: try to access camera directly
|
|
233
|
+
try {
|
|
234
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
235
|
+
stream.getTracks().forEach(track => track.stop());
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
243
|
+
return permission.state === 'granted';
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
return false;
|
|
217
247
|
}
|
|
218
248
|
}
|
|
249
|
+
handleStateChange() {
|
|
250
|
+
// Handle state changes from StateManager if needed
|
|
251
|
+
}
|
|
219
252
|
initializeResizeObserver() {
|
|
220
253
|
if ('ResizeObserver' in window && this.detectionContainer) {
|
|
221
254
|
const resizeObserver = new ResizeObserver(() => {
|
|
@@ -223,15 +256,56 @@ export class JaakStamps {
|
|
|
223
256
|
});
|
|
224
257
|
resizeObserver.observe(this.detectionContainer.parentElement);
|
|
225
258
|
}
|
|
259
|
+
// Handle mobile device orientation changes
|
|
260
|
+
window.addEventListener('orientationchange', () => {
|
|
261
|
+
// Clear any existing orientation timer
|
|
262
|
+
if (this.orientationTimer) {
|
|
263
|
+
clearTimeout(this.orientationTimer);
|
|
264
|
+
}
|
|
265
|
+
// Add delay to ensure video dimensions are updated after rotation
|
|
266
|
+
this.orientationTimer = window.setTimeout(() => {
|
|
267
|
+
this.handleResize();
|
|
268
|
+
this.orientationTimer = undefined;
|
|
269
|
+
}, 300);
|
|
270
|
+
});
|
|
226
271
|
}
|
|
227
272
|
handleResize() {
|
|
228
273
|
if (this.detectionContainer) {
|
|
229
274
|
const container = this.detectionContainer.parentElement;
|
|
230
275
|
const rect = container.getBoundingClientRect();
|
|
231
276
|
this.updateMaskDimensions(rect);
|
|
232
|
-
this.logger.debug('Container redimensionado:', { width: rect.width, height: rect.height });
|
|
233
277
|
}
|
|
234
278
|
}
|
|
279
|
+
triggerRerender() {
|
|
280
|
+
this.captureStateVersion = this.captureStateVersion + 1;
|
|
281
|
+
}
|
|
282
|
+
getGuideText(step) {
|
|
283
|
+
if (step === 'completed') {
|
|
284
|
+
return 'Proceso completado';
|
|
285
|
+
}
|
|
286
|
+
// Check if we should show performance message
|
|
287
|
+
if (this.showPerformanceMessage && step === 'front') {
|
|
288
|
+
return 'Modo manual activado por rendimiento. Use el botón para capturar.';
|
|
289
|
+
}
|
|
290
|
+
if (step === 'front') {
|
|
291
|
+
// Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
|
|
292
|
+
if (this.showManualCaptureButton) {
|
|
293
|
+
return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
|
|
294
|
+
}
|
|
295
|
+
// Instrucción básica de alineación
|
|
296
|
+
return 'Alinee su identificación con el marco';
|
|
297
|
+
}
|
|
298
|
+
else if (step === 'back') {
|
|
299
|
+
// Para el reverso, mostrar instrucciones específicas según el modo
|
|
300
|
+
if (this.useDocumentDetector) {
|
|
301
|
+
return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return 'Alinee su identificación con el marco';
|
|
308
|
+
}
|
|
235
309
|
updateMaskDimensions(containerRect) {
|
|
236
310
|
if (!this.videoRef)
|
|
237
311
|
return;
|
|
@@ -283,16 +357,38 @@ export class JaakStamps {
|
|
|
283
357
|
this.el.style.setProperty('--mask-center-x', `${videoCenterXPercent}%`);
|
|
284
358
|
this.el.style.setProperty('--mask-center-y', `${videoCenterYPercent}%`);
|
|
285
359
|
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
360
|
}
|
|
294
361
|
// PUBLIC METHODS
|
|
362
|
+
// Helper method to check if component is ready for operations
|
|
363
|
+
isComponentReady() {
|
|
364
|
+
if (!this.currentStatus.isInitialized) {
|
|
365
|
+
return {
|
|
366
|
+
ready: false,
|
|
367
|
+
message: 'El componente aún se está inicializando',
|
|
368
|
+
status: 'initializing'
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') {
|
|
372
|
+
return {
|
|
373
|
+
ready: false,
|
|
374
|
+
message: 'El componente está cargando recursos',
|
|
375
|
+
status: 'loading'
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (!this.serviceContainer || !this.stateManager || !this.cameraService || !this.detectionService) {
|
|
379
|
+
return {
|
|
380
|
+
ready: false,
|
|
381
|
+
message: 'Los servicios del componente no están disponibles',
|
|
382
|
+
status: 'error'
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
return { ready: true };
|
|
386
|
+
}
|
|
295
387
|
async getCapturedImages() {
|
|
388
|
+
const readyCheck = this.isComponentReady();
|
|
389
|
+
if (!readyCheck.ready) {
|
|
390
|
+
throw new Error(readyCheck.message);
|
|
391
|
+
}
|
|
296
392
|
const state = this.stateManager.getCaptureState();
|
|
297
393
|
if (state.step !== 'completed') {
|
|
298
394
|
throw new Error('El proceso de captura no ha sido completado');
|
|
@@ -300,146 +396,359 @@ export class JaakStamps {
|
|
|
300
396
|
return this.stateManager.getCapturedImages();
|
|
301
397
|
}
|
|
302
398
|
async isProcessCompleted() {
|
|
399
|
+
const readyCheck = this.isComponentReady();
|
|
400
|
+
if (!readyCheck.ready) {
|
|
401
|
+
return false; // Return false instead of error for this method
|
|
402
|
+
}
|
|
303
403
|
return this.stateManager.isProcessCompleted();
|
|
304
404
|
}
|
|
305
405
|
async startCapture() {
|
|
306
|
-
|
|
406
|
+
const readyCheck = this.isComponentReady();
|
|
407
|
+
if (!readyCheck.ready) {
|
|
408
|
+
this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
|
|
409
|
+
return { success: false, error: readyCheck.message };
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
await this.startDetection();
|
|
413
|
+
return { success: true };
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
this.updateStatus('Error al iniciar captura', error.message, 'error');
|
|
417
|
+
return { success: false, error: error.message };
|
|
418
|
+
}
|
|
307
419
|
}
|
|
308
420
|
async stopCapture() {
|
|
309
|
-
this.
|
|
421
|
+
const readyCheck = this.isComponentReady();
|
|
422
|
+
if (!readyCheck.ready) {
|
|
423
|
+
return { success: false, error: readyCheck.message };
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
this.exitSession();
|
|
427
|
+
return { success: true };
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
return { success: false, error: error.message };
|
|
431
|
+
}
|
|
310
432
|
}
|
|
311
433
|
async resetCapture() {
|
|
312
|
-
this.
|
|
434
|
+
const readyCheck = this.isComponentReady();
|
|
435
|
+
if (!readyCheck.ready) {
|
|
436
|
+
return { success: false, error: readyCheck.message };
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
this.resetDetection();
|
|
440
|
+
return { success: true };
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
return { success: false, error: error.message };
|
|
444
|
+
}
|
|
313
445
|
}
|
|
314
446
|
async skipBackCapture() {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
447
|
+
console.log('🟡 Botón clicked: Saltar reverso');
|
|
448
|
+
// Set processing state immediately
|
|
449
|
+
this.processingButton = 'skip-back';
|
|
450
|
+
// Add small delay to show spinner
|
|
451
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
452
|
+
const readyCheck = this.isComponentReady();
|
|
453
|
+
if (!readyCheck.ready) {
|
|
454
|
+
this.processingButton = null;
|
|
455
|
+
return { success: false, error: readyCheck.message };
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
const captureState = this.stateManager.getCaptureState();
|
|
459
|
+
const capturedImages = this.stateManager.getCapturedImages();
|
|
460
|
+
if (captureState.step === 'back' && capturedImages.front.fullFrame) {
|
|
461
|
+
this.completeProcess(true);
|
|
462
|
+
return { success: true };
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
return { success: false, error: 'No se puede saltar el reverso en el estado actual' };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
return { success: false, error: error.message };
|
|
470
|
+
}
|
|
471
|
+
finally {
|
|
472
|
+
// Always clear processing state when done
|
|
473
|
+
this.processingButton = null;
|
|
319
474
|
}
|
|
320
475
|
}
|
|
321
476
|
async getStatus() {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
477
|
+
const readyCheck = this.isComponentReady();
|
|
478
|
+
if (!readyCheck.ready) {
|
|
479
|
+
return {
|
|
480
|
+
isVideoActive: false,
|
|
481
|
+
captureStep: 'front',
|
|
482
|
+
hasImages: false,
|
|
483
|
+
isProcessCompleted: false,
|
|
484
|
+
isModelPreloaded: false,
|
|
485
|
+
componentStatus: readyCheck.status,
|
|
486
|
+
componentMessage: readyCheck.message
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
const captureState = this.stateManager.getCaptureState();
|
|
491
|
+
const capturedImages = this.stateManager.getCapturedImages();
|
|
492
|
+
return {
|
|
493
|
+
isVideoActive: captureState.isVideoActive,
|
|
494
|
+
captureStep: captureState.step,
|
|
495
|
+
hasImages: !!(capturedImages.front.fullFrame || capturedImages.back.fullFrame),
|
|
496
|
+
isProcessCompleted: this.stateManager.isProcessCompleted(),
|
|
497
|
+
isModelPreloaded: this.detectionService.isModelLoaded(),
|
|
498
|
+
componentStatus: 'ready'
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
return {
|
|
503
|
+
isVideoActive: false,
|
|
504
|
+
captureStep: 'front',
|
|
505
|
+
hasImages: false,
|
|
506
|
+
isProcessCompleted: false,
|
|
507
|
+
isModelPreloaded: false,
|
|
508
|
+
componentStatus: 'error',
|
|
509
|
+
componentMessage: error.message
|
|
510
|
+
};
|
|
511
|
+
}
|
|
331
512
|
}
|
|
332
513
|
async preloadModel() {
|
|
514
|
+
const readyCheck = this.isComponentReady();
|
|
515
|
+
if (!readyCheck.ready) {
|
|
516
|
+
this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
|
|
517
|
+
return { success: false, error: readyCheck.message };
|
|
518
|
+
}
|
|
333
519
|
if (this.detectionService.isModelLoaded()) {
|
|
334
|
-
this.
|
|
335
|
-
this.updateStatus('Modelos ya cargados', '', 'ready');
|
|
520
|
+
this.updateStatus('Reconocimiento listo', '', 'ready');
|
|
336
521
|
return { success: true, message: 'Model already loaded' };
|
|
337
522
|
}
|
|
338
523
|
try {
|
|
339
524
|
const loadStartTime = performance.now();
|
|
340
|
-
this.updateStatus('
|
|
341
|
-
this.stateManager
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
525
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
|
|
526
|
+
if (this.stateManager) {
|
|
527
|
+
this.stateManager.updateCaptureState({ isLoading: true });
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
await this.detectionService.loadModel();
|
|
531
|
+
}
|
|
532
|
+
catch (modelError) {
|
|
533
|
+
this.switchToManualModeWithError('Error al cargar modelo de detección');
|
|
534
|
+
throw modelError;
|
|
535
|
+
}
|
|
536
|
+
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
537
|
+
try {
|
|
538
|
+
await this.detectionService.loadClassificationModel();
|
|
539
|
+
}
|
|
540
|
+
catch (classificationError) {
|
|
541
|
+
this.switchToManualModeWithError('Error al cargar modelo de clasificación');
|
|
542
|
+
throw classificationError;
|
|
543
|
+
}
|
|
345
544
|
const loadEndTime = performance.now();
|
|
346
545
|
const loadTime = loadEndTime - loadStartTime;
|
|
347
546
|
// Record ONNX load time
|
|
348
547
|
if (this.debug) {
|
|
349
548
|
this.recordOnnxPerformance(loadTime, 0);
|
|
350
549
|
}
|
|
351
|
-
this.updateStatus('
|
|
550
|
+
this.updateStatus('Finalizando configuración...', 'Preparando herramientas para captura', 'loading');
|
|
352
551
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
353
|
-
this.updateStatus('
|
|
354
|
-
this.stateManager
|
|
552
|
+
this.updateStatus('Reconocimiento preparado', '', 'ready');
|
|
553
|
+
if (this.stateManager) {
|
|
554
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
555
|
+
}
|
|
355
556
|
this.emitReadyEvent();
|
|
356
|
-
this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', { loadTime: Math.round(loadTime) });
|
|
357
557
|
return { success: true, message: 'Models preloaded successfully' };
|
|
358
558
|
}
|
|
359
559
|
catch (error) {
|
|
360
|
-
this.
|
|
361
|
-
this.
|
|
362
|
-
|
|
560
|
+
this.updateStatus('Error de configuración', 'No se pudieron preparar las herramientas necesarias', 'error');
|
|
561
|
+
if (this.stateManager) {
|
|
562
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
563
|
+
}
|
|
363
564
|
return { success: false, error: error.message };
|
|
364
565
|
}
|
|
365
566
|
}
|
|
366
567
|
async getCameraInfo() {
|
|
367
|
-
|
|
568
|
+
const readyCheck = this.isComponentReady();
|
|
569
|
+
if (!readyCheck.ready) {
|
|
570
|
+
return {
|
|
571
|
+
availableCameras: [],
|
|
572
|
+
selectedCameraId: null,
|
|
573
|
+
deviceType: 'desktop',
|
|
574
|
+
isMultipleCamerasAvailable: false,
|
|
575
|
+
preferredFacing: null,
|
|
576
|
+
error: readyCheck.message
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
return await this.cameraService.getCameraInfo();
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
return {
|
|
584
|
+
availableCameras: [],
|
|
585
|
+
selectedCameraId: null,
|
|
586
|
+
deviceType: 'desktop',
|
|
587
|
+
isMultipleCamerasAvailable: false,
|
|
588
|
+
preferredFacing: null,
|
|
589
|
+
error: error.message
|
|
590
|
+
};
|
|
591
|
+
}
|
|
368
592
|
}
|
|
369
593
|
async setPreferredCamera(camera) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
594
|
+
const readyCheck = this.isComponentReady();
|
|
595
|
+
if (!readyCheck.ready) {
|
|
596
|
+
return {
|
|
597
|
+
success: false,
|
|
598
|
+
error: readyCheck.message,
|
|
599
|
+
selectedCamera: null,
|
|
600
|
+
availableCameras: 0
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
this.preferredCamera = camera;
|
|
605
|
+
this.serviceContainer.updateConfig({ preferredCamera: camera });
|
|
606
|
+
await this.cameraService.enumerateDevices();
|
|
607
|
+
await this.updateCameraInfoWithAutofocus();
|
|
608
|
+
const captureState = this.stateManager.getCaptureState();
|
|
609
|
+
if (captureState.isVideoActive) {
|
|
610
|
+
const selectedCameraId = this.cameraService.getSelectedCameraId();
|
|
611
|
+
if (selectedCameraId) {
|
|
612
|
+
await this.cameraService.switchCamera(selectedCameraId);
|
|
613
|
+
}
|
|
378
614
|
}
|
|
615
|
+
return {
|
|
616
|
+
success: true,
|
|
617
|
+
selectedCamera: this.cameraService.getSelectedCameraId(),
|
|
618
|
+
availableCameras: this.cameraService.getAvailableCameras().length
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
return {
|
|
623
|
+
success: false,
|
|
624
|
+
error: error.message,
|
|
625
|
+
selectedCamera: null,
|
|
626
|
+
availableCameras: 0
|
|
627
|
+
};
|
|
379
628
|
}
|
|
380
|
-
return {
|
|
381
|
-
success: true,
|
|
382
|
-
selectedCamera: this.cameraService.getSelectedCameraId(),
|
|
383
|
-
availableCameras: this.cameraService.getAvailableCameras().length
|
|
384
|
-
};
|
|
385
629
|
}
|
|
386
630
|
async setCaptureDelay(delay) {
|
|
387
631
|
if (delay < 0 || delay > 10000) {
|
|
388
|
-
|
|
632
|
+
return {
|
|
633
|
+
success: false,
|
|
634
|
+
error: 'Capture delay must be between 0 and 10000 milliseconds',
|
|
635
|
+
captureDelay: this.captureDelay
|
|
636
|
+
};
|
|
389
637
|
}
|
|
638
|
+
// Allow setting capture delay even during initialization
|
|
390
639
|
this.captureDelay = delay;
|
|
391
|
-
this.serviceContainer
|
|
640
|
+
if (this.serviceContainer) {
|
|
641
|
+
this.serviceContainer.updateConfig({ captureDelay: delay });
|
|
642
|
+
}
|
|
392
643
|
return {
|
|
393
644
|
success: true,
|
|
394
645
|
captureDelay: this.captureDelay
|
|
395
646
|
};
|
|
396
647
|
}
|
|
397
648
|
async getCaptureDelay() {
|
|
649
|
+
// Always allow getting capture delay, even during initialization
|
|
398
650
|
return this.captureDelay;
|
|
399
651
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
async focusAtPoint(x, y) {
|
|
408
|
-
const focused = await this.cameraService.focusAtPoint(x, y, this.videoStream);
|
|
652
|
+
// Memory and device capability detection
|
|
653
|
+
checkDeviceCapabilities() {
|
|
654
|
+
const deviceMemory = navigator.deviceMemory;
|
|
655
|
+
// Siempre intentar usar ML, independientemente del dispositivo
|
|
656
|
+
// Las restricciones anteriores no eran confiables
|
|
409
657
|
return {
|
|
410
|
-
|
|
411
|
-
|
|
658
|
+
canUseML: true,
|
|
659
|
+
reason: 'Dispositivo compatible con detección automática.',
|
|
660
|
+
memoryMB: deviceMemory ? deviceMemory * 1024 : null
|
|
412
661
|
};
|
|
413
662
|
}
|
|
414
663
|
// DETECTION METHODS
|
|
415
664
|
async startDetection() {
|
|
416
|
-
this.logger.state('INICIANDO_DETECCION');
|
|
417
665
|
try {
|
|
666
|
+
// Verificar capacidades del dispositivo antes de cargar modelos
|
|
667
|
+
const capabilities = this.checkDeviceCapabilities();
|
|
668
|
+
// Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
|
|
669
|
+
if (!capabilities.canUseML && this.useDocumentDetector) {
|
|
670
|
+
this.switchToManualModeWithWarning(capabilities.reason);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
418
673
|
// Paso 1: Verificar y cargar modelos si es necesario
|
|
419
674
|
if (!this.detectionService.isModelLoaded()) {
|
|
420
675
|
const loadStartTime = performance.now();
|
|
421
|
-
this.updateStatus('
|
|
676
|
+
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
|
|
422
677
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
678
|
+
try {
|
|
679
|
+
await this.detectionService.loadModel();
|
|
680
|
+
}
|
|
681
|
+
catch (modelError) {
|
|
682
|
+
// Si es un error de memoria, cambiar a modo manual con mensaje específico
|
|
683
|
+
if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
|
|
684
|
+
const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
|
|
685
|
+
this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
this.switchToManualModeWithError('Error al cargar modelo de detección');
|
|
690
|
+
throw modelError;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
694
|
+
try {
|
|
695
|
+
await this.detectionService.loadClassificationModel();
|
|
696
|
+
}
|
|
697
|
+
catch (classificationError) {
|
|
698
|
+
// Si es un error de memoria, cambiar a modo manual con mensaje específico
|
|
699
|
+
if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
|
|
700
|
+
const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
|
|
701
|
+
this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
this.switchToManualModeWithError('Error al cargar modelo de clasificación');
|
|
706
|
+
throw classificationError;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
426
709
|
const loadEndTime = performance.now();
|
|
427
710
|
const loadTime = loadEndTime - loadStartTime;
|
|
428
711
|
// Record ONNX load time
|
|
429
712
|
if (this.debug) {
|
|
430
713
|
this.recordOnnxPerformance(loadTime, 0);
|
|
431
714
|
}
|
|
432
|
-
this.logger.state('MODELOS_CARGADOS_EN_DETECCION', { loadTime: Math.round(loadTime) });
|
|
433
715
|
}
|
|
434
716
|
// Paso 2: Detectar y configurar dispositivos
|
|
435
|
-
this.updateStatus('
|
|
436
|
-
|
|
717
|
+
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
718
|
+
try {
|
|
719
|
+
await this.cameraService.enumerateDevices();
|
|
720
|
+
}
|
|
721
|
+
catch (enumerateError) {
|
|
722
|
+
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
await this.updateCameraInfoWithAutofocus();
|
|
726
|
+
}
|
|
727
|
+
catch (cameraInfoError) {
|
|
728
|
+
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
729
|
+
}
|
|
437
730
|
// Paso 3: Configurar cámara seleccionada
|
|
438
|
-
this.updateStatus('
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
731
|
+
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
732
|
+
let stream;
|
|
733
|
+
try {
|
|
734
|
+
stream = await this.cameraService.setupCamera();
|
|
735
|
+
}
|
|
736
|
+
catch (setupError) {
|
|
737
|
+
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
738
|
+
}
|
|
739
|
+
// Paso 4: Inicializar video y mostrar estado de análisis inicial
|
|
740
|
+
if (this.useDocumentDetector) {
|
|
741
|
+
this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
await this.initializeVideoStream(stream);
|
|
748
|
+
}
|
|
749
|
+
catch (videoError) {
|
|
750
|
+
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
751
|
+
}
|
|
443
752
|
// Paso 5: Calibrar máscara
|
|
444
753
|
if (this.detectionContainer) {
|
|
445
754
|
const container = this.detectionContainer.parentElement;
|
|
@@ -449,11 +758,14 @@ export class JaakStamps {
|
|
|
449
758
|
// Finalizar e iniciar captura
|
|
450
759
|
this.startTime = Date.now();
|
|
451
760
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
761
|
+
// Mostrar botón manual si el detector está deshabilitado
|
|
762
|
+
if (!this.useDocumentDetector) {
|
|
763
|
+
this.showManualCaptureButton = true;
|
|
764
|
+
}
|
|
452
765
|
this.detectFrame();
|
|
453
766
|
}
|
|
454
767
|
catch (err) {
|
|
455
|
-
this.
|
|
456
|
-
this.updateStatus('Error al iniciar captura', 'No se pudo completar la inicialización', 'error');
|
|
768
|
+
this.updateStatus('Error al iniciar', 'No se pudo preparar la captura', 'error');
|
|
457
769
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
458
770
|
}
|
|
459
771
|
}
|
|
@@ -463,14 +775,16 @@ export class JaakStamps {
|
|
|
463
775
|
this.videoStream = stream;
|
|
464
776
|
const isRear = this.cameraService.isRearCamera(stream);
|
|
465
777
|
this.shouldMirrorVideo = !isRear;
|
|
466
|
-
this.logger.state('CAMARA_CONFIGURADA', {
|
|
467
|
-
isRearCamera: isRear,
|
|
468
|
-
shouldMirrorVideo: this.shouldMirrorVideo
|
|
469
|
-
});
|
|
470
778
|
return new Promise((resolve) => {
|
|
471
779
|
this.videoRef.onloadedmetadata = async () => {
|
|
472
780
|
await this.videoRef.play();
|
|
473
781
|
this.stateManager.updateCaptureState({ isVideoActive: true });
|
|
782
|
+
// Recalculate mask dimensions when new camera stream loads
|
|
783
|
+
if (this.detectionContainer) {
|
|
784
|
+
const container = this.detectionContainer.parentElement;
|
|
785
|
+
const rect = container.getBoundingClientRect();
|
|
786
|
+
this.updateMaskDimensions(rect);
|
|
787
|
+
}
|
|
474
788
|
resolve();
|
|
475
789
|
};
|
|
476
790
|
});
|
|
@@ -485,15 +799,28 @@ export class JaakStamps {
|
|
|
485
799
|
const captureState = this.stateManager.getCaptureState();
|
|
486
800
|
if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
|
|
487
801
|
return;
|
|
802
|
+
// Show manual capture button when document detector is disabled or performance is degraded
|
|
803
|
+
if (!this.useDocumentDetector || this.performanceDegradedMode) {
|
|
804
|
+
this.showManualCaptureButton = true;
|
|
805
|
+
// Continue the loop for UI updates but skip detection logic
|
|
806
|
+
if (captureState.step !== 'completed') {
|
|
807
|
+
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
808
|
+
}
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
else if (!this.performanceDegradedMode) {
|
|
812
|
+
this.showManualCaptureButton = false;
|
|
813
|
+
}
|
|
488
814
|
if (captureState.isDetectionPaused) {
|
|
489
815
|
if (captureState.step !== 'completed') {
|
|
490
816
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
491
817
|
}
|
|
492
818
|
return;
|
|
493
819
|
}
|
|
494
|
-
//
|
|
820
|
+
// Adaptive frame skipping for performance
|
|
495
821
|
this.frameSkipCounter++;
|
|
496
|
-
|
|
822
|
+
const currentFrameSkip = this.getAdaptiveFrameSkip();
|
|
823
|
+
if (this.frameSkipCounter <= currentFrameSkip) {
|
|
497
824
|
if (captureState.step !== 'completed') {
|
|
498
825
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
499
826
|
}
|
|
@@ -511,10 +838,27 @@ export class JaakStamps {
|
|
|
511
838
|
this.lastInferenceTime = currentTime;
|
|
512
839
|
// Measure preprocessing and inference time
|
|
513
840
|
const inferenceStartTime = performance.now();
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
841
|
+
let inputTensor;
|
|
842
|
+
let detections;
|
|
843
|
+
let inferenceTime;
|
|
844
|
+
try {
|
|
845
|
+
inputTensor = this.detectionService.preprocess(this.videoRef);
|
|
846
|
+
}
|
|
847
|
+
catch (preprocessError) {
|
|
848
|
+
this.switchToManualModeWithError('Error al procesar imagen');
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
// Standard detection without timeout (timeout is handled by frame counting)
|
|
853
|
+
detections = await this.detectionService.runInference(inputTensor);
|
|
854
|
+
inferenceTime = performance.now() - inferenceStartTime;
|
|
855
|
+
}
|
|
856
|
+
catch (inferenceError) {
|
|
857
|
+
this.switchToManualModeWithError('Error en detección automática');
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
// Increment frame count
|
|
861
|
+
this.processedFramesCount++;
|
|
518
862
|
// Record ONNX performance metrics
|
|
519
863
|
if (this.debug) {
|
|
520
864
|
this.recordOnnxPerformance(0, inferenceTime);
|
|
@@ -553,9 +897,30 @@ export class JaakStamps {
|
|
|
553
897
|
}
|
|
554
898
|
this.updateMaskColor(detections);
|
|
555
899
|
// Record frame processing metrics
|
|
900
|
+
const frameEndTime = performance.now();
|
|
901
|
+
const totalFrameTime = frameEndTime - frameStartTime;
|
|
902
|
+
// Sistema simplificado: Verificar rendimiento de cada frame (solo si no se ha cambiado a manual)
|
|
903
|
+
if (this.useDocumentDetector && !this.hasAutoSwitchedToManual) {
|
|
904
|
+
// Verificar si el frame es lento
|
|
905
|
+
if (totalFrameTime >= this.PERFORMANCE_THRESHOLD_MS) {
|
|
906
|
+
this.slowFrameCount++;
|
|
907
|
+
// Si tenemos suficientes frames lentos consecutivos, cambiar a manual
|
|
908
|
+
if (this.slowFrameCount >= this.SLOW_FRAMES_TO_TRIGGER) {
|
|
909
|
+
this.switchToManualMode();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
// Frame rápido, reiniciar contador
|
|
914
|
+
if (this.slowFrameCount > 0) {
|
|
915
|
+
this.slowFrameCount = 0;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
// Cambiar mensaje de estado después de analizar los primeros 2 frames
|
|
919
|
+
if (this.processedFramesCount === 2) {
|
|
920
|
+
this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
|
|
921
|
+
}
|
|
922
|
+
}
|
|
556
923
|
if (this.debug) {
|
|
557
|
-
const frameEndTime = performance.now();
|
|
558
|
-
const totalFrameTime = frameEndTime - frameStartTime;
|
|
559
924
|
this.recordFrameProcessing(totalFrameTime, detections.length);
|
|
560
925
|
}
|
|
561
926
|
// Continue detection loop
|
|
@@ -569,7 +934,6 @@ export class JaakStamps {
|
|
|
569
934
|
}
|
|
570
935
|
}
|
|
571
936
|
catch (e) {
|
|
572
|
-
this.logger.error('Error en inferencia de modelo:', e);
|
|
573
937
|
const captureState = this.stateManager.getCaptureState();
|
|
574
938
|
if (captureState.step !== 'completed') {
|
|
575
939
|
setTimeout(() => this.detectFrame(), 100);
|
|
@@ -592,10 +956,18 @@ export class JaakStamps {
|
|
|
592
956
|
clearTimeout(this.alignmentTimer);
|
|
593
957
|
this.alignmentTimer = undefined;
|
|
594
958
|
}
|
|
959
|
+
if (this.orientationTimer) {
|
|
960
|
+
clearTimeout(this.orientationTimer);
|
|
961
|
+
this.orientationTimer = undefined;
|
|
962
|
+
}
|
|
963
|
+
// Liberar recursos de ONNX al limpiar el componente
|
|
964
|
+
this.releaseOnnxResources();
|
|
595
965
|
if (this.performanceUpdateInterval) {
|
|
596
966
|
clearInterval(this.performanceUpdateInterval);
|
|
597
967
|
this.performanceUpdateInterval = undefined;
|
|
598
968
|
}
|
|
969
|
+
// Clear canvas pool
|
|
970
|
+
this.canvasPool = [];
|
|
599
971
|
this.detectionBoxes = [];
|
|
600
972
|
this.alignmentStartTime = undefined;
|
|
601
973
|
this.hasDocumentDetected = false;
|
|
@@ -610,14 +982,8 @@ export class JaakStamps {
|
|
|
610
982
|
step: 'front',
|
|
611
983
|
isCapturing: false
|
|
612
984
|
};
|
|
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: {
|
|
985
|
+
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
986
|
+
return (h("div", { key: 'f9cbca2fd6b8548a29ace7fcb972b53f402f61af', class: "detector-container" }, h("div", { key: '4f3cac734eb6bfd44a8de644eadca17e7893e11a', class: "video-container" }, h("video", { key: '5e15a38c989a72d5274281272d41ba064447ca6f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '38c8ad8b691db60e9b5a2a8b641fe6febbeacfca', 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
987
|
position: 'absolute',
|
|
622
988
|
left: `${box.x}px`,
|
|
623
989
|
top: `${box.y}px`,
|
|
@@ -626,9 +992,9 @@ export class JaakStamps {
|
|
|
626
992
|
border: '2px solid #32406C',
|
|
627
993
|
pointerEvents: 'none',
|
|
628
994
|
boxSizing: 'border-box'
|
|
629
|
-
} })))), this.isMaskReady && (h("div", { key: '
|
|
995
|
+
} })))), this.isMaskReady && (h("div", { key: 'c56fde56b7a86a4e8a4cf558745291ea5cde12fd', class: "overlay-mask" }, h("div", { key: '04ed3f01f271dc84eed594cb4b60c2453645bd41', class: "card-outline" }, h("div", { key: '648bd423a5e886d4768115237de5896c7223b30b', class: "side side-top" }), h("div", { key: 'b8878059493ca61961b9b3c82ecc18b50b79ac85', class: "side side-right" }), h("div", { key: '09e5ad64ff33dbd5c112bf52b71e14409ab6aa65', class: "side side-bottom" }), h("div", { key: 'b8ba3d83c9bc1b4a0da859d7ed4b9a15eaddcc2a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '3279701a240f77ac96e68a34ad95c099340c9a6a', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '2adf96d0a57e482372fd9586229dac2301b64fd4', class: "back-capture-section" }, h("div", { key: '305a147c9cfee854c60ac946a48a1d21b2792725', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (h("button", { key: '43270a0647f39307229c540dd5130fb58b1ff4fb', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: 'eb4666603774564eef95a120ac13faeb9e10c8b4', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: 'cac667d9548ae2314ad5a677c5ed90f25184f728', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: '0af7aa4106ce189ae2d79bec78ede01abe25b1e7', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
630
996
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
631
|
-
: 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: '
|
|
997
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'de6e5c4fad4d4d996b894457c9af52bc71e533fb', class: "camera-controls" }, h("button", { key: 'cfddcf2420d5f5923a6b02ca446bbe1b8e9fc5e4', 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: '5fb73f958b9a5a6f9242032c4530b0bdab84f6c5', class: "camera-selector-dropdown" }, h("div", { key: '8ee2d26b1001e571e7b6c326e927926fa08287fd', class: "camera-selector-header" }, h("span", { key: '78350b0fb1bd933f7c8c59d8dab0b7122ffbe363' }, "Seleccionar C\u00E1mara"), h("button", { key: 'bb750e0ddd5a1ee5ff96bd57805a7f90b8821e9f', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'd0f6343cf1b7367f3da33ec8cf64891109d923b8', 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: 'c9642fbfb0acea766d3f0542f4cc9f17f4af4d7b', class: "device-info" }, h("small", { key: 'b6e9507ff7addf011f339f6c22a88a96b40db68a' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '04ae9262409284782850b90945eb7038673e2097', class: "manual-capture-section" }, h("button", { key: '27c315e503ccba468b89ebd58d6b70f40ddd06ba', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '8f2dcd2df9a218b37eb0b8204a8724362cfeaa53', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: '6fd6fe1d0213b01c8119a67fb8108f73227feee1', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'fba3816b48b99ca64510ecbefc0d41431569c9ae', class: "flip-animation" }, h("div", { key: 'beea1c8245b5d81f1008efc6d1b079a61c821e7f', class: "id-card-icon" }), h("div", { key: 'f0fbc34f619a653e2c0ade764ca99e7aa582d157', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'e4a0e6d31112e3a50024d1d0430b4ad997049fe7', class: "success-animation" }, h("div", { key: '5dd3f781f55630bc904c81207548ae35bc79b452', class: "check-icon" }), h("div", { key: 'a10ebc557582325091efb0abd12d62b27a43b98c', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '975865955dee1803b5215ecf049701c98b324855', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '84f6f67202e26ce80f7fc5d5f321e23b5bc55c57', class: "status-spinner" })), h("div", { key: 'a1ae56ed156e8feb279a1bf160f749c5d0b5ce45', class: "status-content" }, h("div", { key: 'd62932554cea257ce19763df26e0ba702e50eeca', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'c5506fa12dd0d37d26dc26c9f2381c843330451f', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '2097eda63185f336f7319014fae2ef6db1e41761', class: "performance-monitor" }, h("div", { key: 'd611c494c6b1a5930ef64aebdcba41e5d94c9ee6', class: "performance-expanded" }, h("div", { key: '800d46831e7b9bec260d9c5e67ea1a3eb9c2933e', class: "metrics-row" }, h("div", { key: 'dd0db1faa9050d98e71da4b5cd26a4a484c37c64', class: "metric-compact" }, h("span", { key: 'f0e6ea61a35ddb2ae5c2a6aff8c255a02c5c1219', class: "metric-label" }, "FPS"), h("span", { key: 'cbf4df4d142f6f01e45f566a8ede8fbbf29ad673', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '83861ad31fa46c8199a5583e4b3f8eefd44bacd6', class: "metric-compact" }, h("span", { key: '41b8de4cda47959dfc9df393e95fdadde534dd21', class: "metric-label" }, "MEM"), h("span", { key: 'b268f13cc78066e240ed158f1443113a94827aff', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '9f9a9d0690906b186492af23b900c5fa0f5cff89', class: "metrics-row" }, h("div", { key: '84fa6e453dc36e4a74e8b573447b17230766229e', class: "metric-compact" }, h("span", { key: 'f17d52899090c89f532d74c552dd8c5338fc2d83', class: "metric-label" }, "INF"), h("span", { key: '2beca0d7ce55ed574f6b01efc086c5846983e024', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '981e300cf076e81bfe946974f6cffd63619eacde', class: "metric-compact" }, h("span", { key: '3dfb0c996a69eb87b0f711322675cad69c471146', class: "metric-label" }, "FRAME"), h("span", { key: '3ae2ac6e90cda77f23e559f3197c3b761f9bdc40', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '2c51216838d87728d9d7764e5ba8a50fa02eb493', class: "metrics-row" }, h("div", { key: 'eda810ffb80ec38ab84583f59b26706f90a50fc9', class: "metric-compact" }, h("span", { key: '978ba9c0c67d6da6b29b3535af33a314bab9276a', class: "metric-label" }, "DET"), h("span", { key: '3e5e169fd4a3906283d8f918b5bbc7c5df6ccd0f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'b4b915c745e35c09470a4dc5edfc112d32cfb8b6', class: "metric-compact" }, h("span", { key: '3e2a7b88a4446813c955efb0dfa9e2958e8e1448', class: "metric-label" }, "RATE"), h("span", { key: 'f4f073f7ee622167a241c6a1c535ef580c13d9ab', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '76c556e92f6d7980539f1a30efe8751f665164fb', class: "watermark" }, h("img", { key: 'af23e115d6b4e92a72e55d6d98fde0e4b1d09e9b', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
632
998
|
}
|
|
633
999
|
// Utility methods
|
|
634
1000
|
updateDetectionBoxes(boxes) {
|
|
@@ -709,35 +1075,18 @@ export class JaakStamps {
|
|
|
709
1075
|
if (allSidesAligned && bestBox) {
|
|
710
1076
|
cardOutline?.classList.add('perfect-match');
|
|
711
1077
|
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
1078
|
if (!this.hasScreenshotTaken) {
|
|
720
1079
|
const currentTime = Date.now();
|
|
721
1080
|
// Initialize alignment start time if not set
|
|
722
1081
|
if (!this.alignmentStartTime) {
|
|
723
1082
|
this.alignmentStartTime = currentTime;
|
|
724
|
-
this.logger.state('ALIGNMENT_TIMER_STARTED', { startTime: currentTime });
|
|
725
1083
|
}
|
|
726
1084
|
// Check if document has been aligned for the configured delay
|
|
727
1085
|
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
1086
|
if (alignmentDuration >= this.captureDelay) {
|
|
734
|
-
this.logger.state('TRIGGERING_CAPTURE', {
|
|
735
|
-
alignmentDuration,
|
|
736
|
-
captureDelay: this.captureDelay
|
|
737
|
-
});
|
|
738
1087
|
this.lastDetectedBox = bestBox;
|
|
739
|
-
this.takeScreenshot().catch(
|
|
740
|
-
|
|
1088
|
+
this.takeScreenshot().catch(() => {
|
|
1089
|
+
// Handle screenshot error silently
|
|
741
1090
|
});
|
|
742
1091
|
this.hasScreenshotTaken = true;
|
|
743
1092
|
this.alignmentStartTime = undefined;
|
|
@@ -760,24 +1109,150 @@ export class JaakStamps {
|
|
|
760
1109
|
}
|
|
761
1110
|
}
|
|
762
1111
|
}
|
|
1112
|
+
async takeManualScreenshot() {
|
|
1113
|
+
console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
|
|
1114
|
+
// Set processing state immediately
|
|
1115
|
+
const captureState = this.stateManager?.getCaptureState();
|
|
1116
|
+
if (captureState?.step === 'front') {
|
|
1117
|
+
this.processingButton = 'capture-front';
|
|
1118
|
+
}
|
|
1119
|
+
else if (captureState?.step === 'back') {
|
|
1120
|
+
this.processingButton = 'capture-back';
|
|
1121
|
+
}
|
|
1122
|
+
console.log('🔵 processingButton set to:', this.processingButton);
|
|
1123
|
+
// Add small delay to show spinner
|
|
1124
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1125
|
+
if (!this.videoRef) {
|
|
1126
|
+
this.processingButton = null;
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
// When using manual capture, use mask coordinates for cropping
|
|
1130
|
+
// Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
|
|
1131
|
+
await this.takeScreenshotWithMaskCoordinates();
|
|
1132
|
+
}
|
|
1133
|
+
async takeScreenshotWithMaskCoordinates() {
|
|
1134
|
+
if (!this.videoRef || !this.detectionContainer)
|
|
1135
|
+
return;
|
|
1136
|
+
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
1137
|
+
this.triggerCaptureAnimation();
|
|
1138
|
+
// Use pooled canvas for optimization
|
|
1139
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
1140
|
+
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
1141
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
1142
|
+
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
1143
|
+
// Calculate mask coordinates for cropping
|
|
1144
|
+
const container = this.detectionContainer.parentElement;
|
|
1145
|
+
const containerRect = container.getBoundingClientRect();
|
|
1146
|
+
// Get mask dimensions from CSS properties
|
|
1147
|
+
const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
|
|
1148
|
+
const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
|
|
1149
|
+
const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
|
|
1150
|
+
const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
|
|
1151
|
+
// Convert percentages to actual video coordinates
|
|
1152
|
+
const videoWidth = this.videoRef.videoWidth;
|
|
1153
|
+
const videoHeight = this.videoRef.videoHeight;
|
|
1154
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
1155
|
+
const containerAspectRatio = containerRect.width / containerRect.height;
|
|
1156
|
+
let displayedVideoWidth, displayedVideoHeight;
|
|
1157
|
+
let videoOffsetX = 0, videoOffsetY = 0;
|
|
1158
|
+
if (videoAspectRatio > containerAspectRatio) {
|
|
1159
|
+
displayedVideoWidth = containerRect.width;
|
|
1160
|
+
displayedVideoHeight = containerRect.width / videoAspectRatio;
|
|
1161
|
+
videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
displayedVideoHeight = containerRect.height;
|
|
1165
|
+
displayedVideoWidth = containerRect.height * videoAspectRatio;
|
|
1166
|
+
videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
|
|
1167
|
+
}
|
|
1168
|
+
// Calculate mask coordinates in video space
|
|
1169
|
+
const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
|
|
1170
|
+
const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
|
|
1171
|
+
const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
|
|
1172
|
+
const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
|
|
1173
|
+
// Convert to video coordinates
|
|
1174
|
+
const scaleX = videoWidth / displayedVideoWidth;
|
|
1175
|
+
const scaleY = videoHeight / displayedVideoHeight;
|
|
1176
|
+
const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
|
|
1177
|
+
const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
|
|
1178
|
+
const maskWidthInVideo = maskWidthInContainer * scaleX;
|
|
1179
|
+
const maskHeightInVideo = maskHeightInContainer * scaleY;
|
|
1180
|
+
// Calculate crop coordinates
|
|
1181
|
+
const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
|
|
1182
|
+
const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
|
|
1183
|
+
const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
|
|
1184
|
+
const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
|
|
1185
|
+
// Use pooled canvas for cropped version
|
|
1186
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
1187
|
+
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
1188
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
1189
|
+
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
1190
|
+
const captureState = this.stateManager.getCaptureState();
|
|
1191
|
+
if (captureState.step === 'front') {
|
|
1192
|
+
this.stateManager.setCapturedImages({
|
|
1193
|
+
front: {
|
|
1194
|
+
fullFrame: captureCanvas.toDataURL('image/jpeg'),
|
|
1195
|
+
cropped: croppedCanvas.toDataURL('image/jpeg')
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
// Check if document classification is enabled (independent of detector)
|
|
1199
|
+
if (this.useDocumentClassification) {
|
|
1200
|
+
try {
|
|
1201
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
1202
|
+
if (classification && classification.class === 'passport') {
|
|
1203
|
+
this.completeProcess(true);
|
|
1204
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1205
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
1206
|
+
// Clear processing button when passport is detected
|
|
1207
|
+
this.processingButton = null;
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
catch (classifyError) {
|
|
1212
|
+
// No cambiar a manual aquí, solo registrar el error y continuar
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
this.stateManager.updateCaptureState({
|
|
1216
|
+
step: 'back',
|
|
1217
|
+
isDetectionPaused: true,
|
|
1218
|
+
showFlipAnimation: true
|
|
1219
|
+
});
|
|
1220
|
+
this.triggerRerender();
|
|
1221
|
+
setTimeout(() => {
|
|
1222
|
+
this.stateManager.updateCaptureState({
|
|
1223
|
+
showFlipAnimation: false,
|
|
1224
|
+
isDetectionPaused: false
|
|
1225
|
+
});
|
|
1226
|
+
this.triggerRerender();
|
|
1227
|
+
this.startBackDocumentTimer();
|
|
1228
|
+
// Clear processing button after animation completes
|
|
1229
|
+
this.processingButton = null;
|
|
1230
|
+
}, 3000);
|
|
1231
|
+
}
|
|
1232
|
+
else if (captureState.step === 'back') {
|
|
1233
|
+
this.stateManager.setCapturedImages({
|
|
1234
|
+
back: {
|
|
1235
|
+
fullFrame: captureCanvas.toDataURL('image/jpeg'),
|
|
1236
|
+
cropped: croppedCanvas.toDataURL('image/jpeg')
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
this.completeProcess(false);
|
|
1240
|
+
// Clear processing button after back capture completes
|
|
1241
|
+
this.processingButton = null;
|
|
1242
|
+
}
|
|
1243
|
+
// Return canvases to pool after use
|
|
1244
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1245
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
1246
|
+
}
|
|
763
1247
|
async takeScreenshot() {
|
|
764
1248
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
765
1249
|
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
1250
|
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
775
1251
|
this.triggerCaptureAnimation();
|
|
776
|
-
//
|
|
777
|
-
const captureCanvas =
|
|
778
|
-
captureCanvas.width = this.videoRef.videoWidth;
|
|
779
|
-
captureCanvas.height = this.videoRef.videoHeight;
|
|
1252
|
+
// Use pooled canvas for optimization
|
|
1253
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
780
1254
|
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
1255
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
781
1256
|
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
782
1257
|
// Calculate crop coordinates
|
|
783
1258
|
const INPUT_SIZE = 320;
|
|
@@ -787,27 +1262,30 @@ export class JaakStamps {
|
|
|
787
1262
|
const cropY = Math.max(0, (this.lastDetectedBox.y * scaleY) - this.cropMargin);
|
|
788
1263
|
const cropWidth = Math.min((this.lastDetectedBox.w * scaleX) + (2 * this.cropMargin), this.videoRef.videoWidth - cropX);
|
|
789
1264
|
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;
|
|
1265
|
+
// Use pooled canvas for cropped version
|
|
1266
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
794
1267
|
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
1268
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
795
1269
|
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
796
1270
|
const captureState = this.stateManager.getCaptureState();
|
|
797
1271
|
if (captureState.step === 'front') {
|
|
798
1272
|
this.stateManager.setCapturedImages({
|
|
799
1273
|
front: {
|
|
800
|
-
fullFrame: captureCanvas.toDataURL('image/
|
|
801
|
-
cropped: croppedCanvas.toDataURL('image/
|
|
1274
|
+
fullFrame: captureCanvas.toDataURL('image/jpeg'),
|
|
1275
|
+
cropped: croppedCanvas.toDataURL('image/jpeg')
|
|
802
1276
|
}
|
|
803
1277
|
});
|
|
804
1278
|
// Check if document classification is enabled
|
|
805
1279
|
if (this.useDocumentClassification) {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1280
|
+
try {
|
|
1281
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
1282
|
+
if (classification && classification.class === 'passport') {
|
|
1283
|
+
this.completeProcess(true);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
catch (classifyError) {
|
|
1288
|
+
// No cambiar a manual aquí, solo registrar el error y continuar
|
|
811
1289
|
}
|
|
812
1290
|
}
|
|
813
1291
|
this.stateManager.updateCaptureState({
|
|
@@ -826,12 +1304,15 @@ export class JaakStamps {
|
|
|
826
1304
|
else if (captureState.step === 'back') {
|
|
827
1305
|
this.stateManager.setCapturedImages({
|
|
828
1306
|
back: {
|
|
829
|
-
fullFrame: captureCanvas.toDataURL('image/
|
|
830
|
-
cropped: croppedCanvas.toDataURL('image/
|
|
1307
|
+
fullFrame: captureCanvas.toDataURL('image/jpeg'),
|
|
1308
|
+
cropped: croppedCanvas.toDataURL('image/jpeg')
|
|
831
1309
|
}
|
|
832
1310
|
});
|
|
833
1311
|
this.completeProcess(false);
|
|
834
1312
|
}
|
|
1313
|
+
// Return canvases to pool after use
|
|
1314
|
+
this.returnCanvasToPool(captureCanvas);
|
|
1315
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
835
1316
|
}
|
|
836
1317
|
triggerCaptureAnimation() {
|
|
837
1318
|
const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
|
|
@@ -841,6 +1322,138 @@ export class JaakStamps {
|
|
|
841
1322
|
cardOutline?.classList.remove('capturing');
|
|
842
1323
|
}, 600);
|
|
843
1324
|
}
|
|
1325
|
+
// Método simplificado para cambio a modo manual
|
|
1326
|
+
switchToManualMode() {
|
|
1327
|
+
// Only switch if detector is enabled and not already switched
|
|
1328
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
// Detener todo el monitoreo de performance
|
|
1332
|
+
this.stopPerformanceMonitoring();
|
|
1333
|
+
// Liberar recursos de ONNX
|
|
1334
|
+
this.releaseOnnxResources();
|
|
1335
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
1336
|
+
this.resetMaskToWhite();
|
|
1337
|
+
this.detectionBoxes = [];
|
|
1338
|
+
this.hasAutoSwitchedToManual = true;
|
|
1339
|
+
this.performanceDegradedMode = true;
|
|
1340
|
+
this.showManualCaptureButton = true;
|
|
1341
|
+
this.showPerformanceMessage = true;
|
|
1342
|
+
// Hide performance message after 5 seconds
|
|
1343
|
+
setTimeout(() => {
|
|
1344
|
+
this.showPerformanceMessage = false;
|
|
1345
|
+
}, 5000);
|
|
1346
|
+
}
|
|
1347
|
+
// Método para cambiar a modo manual por errores de ONNX
|
|
1348
|
+
switchToManualModeWithWarning(warningMessage) {
|
|
1349
|
+
// Solo cambiar si el detector está habilitado y no se ha cambiado ya
|
|
1350
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
// No necesitamos liberar recursos ONNX porque no se han cargado aún
|
|
1354
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
1355
|
+
this.resetMaskToWhite();
|
|
1356
|
+
this.detectionBoxes = [];
|
|
1357
|
+
// Cambiar a modo manual
|
|
1358
|
+
this.hasAutoSwitchedToManual = true;
|
|
1359
|
+
this.performanceDegradedMode = true;
|
|
1360
|
+
this.showManualCaptureButton = true;
|
|
1361
|
+
this.useDocumentDetector = false; // Desactivar detector automático
|
|
1362
|
+
// Actualizar el estado con mensaje informativo (no error)
|
|
1363
|
+
this.updateStatus('Modo manual activado', warningMessage, 'active');
|
|
1364
|
+
// Continuar con la configuración de cámara
|
|
1365
|
+
this.continueWithCameraSetup();
|
|
1366
|
+
}
|
|
1367
|
+
async continueWithCameraSetup() {
|
|
1368
|
+
try {
|
|
1369
|
+
// Paso 2: Detectar y configurar dispositivos
|
|
1370
|
+
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
1371
|
+
try {
|
|
1372
|
+
await this.cameraService.enumerateDevices();
|
|
1373
|
+
}
|
|
1374
|
+
catch (enumerateError) {
|
|
1375
|
+
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
1376
|
+
}
|
|
1377
|
+
try {
|
|
1378
|
+
await this.updateCameraInfoWithAutofocus();
|
|
1379
|
+
}
|
|
1380
|
+
catch (cameraInfoError) {
|
|
1381
|
+
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
1382
|
+
}
|
|
1383
|
+
// Paso 3: Configurar cámara seleccionada
|
|
1384
|
+
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
1385
|
+
let stream;
|
|
1386
|
+
try {
|
|
1387
|
+
stream = await this.cameraService.setupCamera();
|
|
1388
|
+
}
|
|
1389
|
+
catch (setupError) {
|
|
1390
|
+
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
1391
|
+
}
|
|
1392
|
+
// Paso 4: Inicializar video
|
|
1393
|
+
this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
|
|
1394
|
+
try {
|
|
1395
|
+
await this.initializeVideoStream(stream);
|
|
1396
|
+
}
|
|
1397
|
+
catch (videoError) {
|
|
1398
|
+
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
1399
|
+
}
|
|
1400
|
+
// Paso 5: Calibrar máscara
|
|
1401
|
+
if (this.detectionContainer) {
|
|
1402
|
+
const container = this.detectionContainer.parentElement;
|
|
1403
|
+
const rect = container.getBoundingClientRect();
|
|
1404
|
+
this.updateMaskDimensions(rect);
|
|
1405
|
+
}
|
|
1406
|
+
// Finalizar
|
|
1407
|
+
this.startTime = Date.now();
|
|
1408
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1409
|
+
// Mostrar botón manual (ya debe estar habilitado)
|
|
1410
|
+
this.showManualCaptureButton = true;
|
|
1411
|
+
}
|
|
1412
|
+
catch (error) {
|
|
1413
|
+
this.updateStatus('Error de cámara', error.message, 'error');
|
|
1414
|
+
throw error;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
switchToManualModeWithError(errorMessage) {
|
|
1418
|
+
// Solo cambiar si el detector está habilitado y no se ha cambiado ya
|
|
1419
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
// Liberar recursos de ONNX inmediatamente
|
|
1423
|
+
this.releaseOnnxResources();
|
|
1424
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
1425
|
+
this.resetMaskToWhite();
|
|
1426
|
+
this.detectionBoxes = [];
|
|
1427
|
+
// Cambiar a modo manual
|
|
1428
|
+
this.hasAutoSwitchedToManual = true;
|
|
1429
|
+
this.performanceDegradedMode = true;
|
|
1430
|
+
this.showManualCaptureButton = true;
|
|
1431
|
+
this.useDocumentDetector = false; // Desactivar detector automático
|
|
1432
|
+
// Actualizar el estado con mensaje de error
|
|
1433
|
+
this.updateStatus('Modo manual activado', errorMessage, 'error');
|
|
1434
|
+
// Continuar con la configuración de cámara para mostrar el video
|
|
1435
|
+
this.continueWithCameraSetup().catch(() => {
|
|
1436
|
+
// Si falla la configuración de cámara, mantener el mensaje de error pero cambiar el estado
|
|
1437
|
+
setTimeout(() => {
|
|
1438
|
+
this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
|
|
1439
|
+
}, 5000);
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
stopPerformanceMonitoring() {
|
|
1443
|
+
// Nota: No necesitamos limpiar más variables porque ya no se usarán
|
|
1444
|
+
// El sistema automáticamente saltará la lógica de performance una vez que hasAutoSwitchedToManual = true
|
|
1445
|
+
}
|
|
1446
|
+
// Método para liberar recursos de ONNX cuando se cambia a modo manual
|
|
1447
|
+
releaseOnnxResources() {
|
|
1448
|
+
try {
|
|
1449
|
+
// Liberar recursos del servicio de detección
|
|
1450
|
+
if (this.detectionService) {
|
|
1451
|
+
this.detectionService.cleanup();
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
catch (error) {
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
844
1457
|
completeProcess(skippedBack = false) {
|
|
845
1458
|
this.stateManager.updateCaptureState({
|
|
846
1459
|
step: 'completed',
|
|
@@ -860,11 +1473,6 @@ export class JaakStamps {
|
|
|
860
1473
|
setTimeout(() => {
|
|
861
1474
|
this.stateManager.updateCaptureState({ showSuccessAnimation: false });
|
|
862
1475
|
}, 3000);
|
|
863
|
-
this.logger.state('PROCESO_COMPLETADO', {
|
|
864
|
-
skippedBack,
|
|
865
|
-
totalImages: capturedImages.metadata.totalImages,
|
|
866
|
-
timestamp: new Date().toISOString()
|
|
867
|
-
});
|
|
868
1476
|
}
|
|
869
1477
|
stopDetection() {
|
|
870
1478
|
if (this.animationId) {
|
|
@@ -873,7 +1481,6 @@ export class JaakStamps {
|
|
|
873
1481
|
}
|
|
874
1482
|
this.clearBackDocumentTimer();
|
|
875
1483
|
this.detectionBoxes = [];
|
|
876
|
-
this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
|
|
877
1484
|
}
|
|
878
1485
|
startBackDocumentTimer() {
|
|
879
1486
|
if (!this.enableBackDocumentTimer)
|
|
@@ -902,17 +1509,28 @@ export class JaakStamps {
|
|
|
902
1509
|
return; // Don't toggle if switching camera
|
|
903
1510
|
this.showCameraSelector = !this.showCameraSelector;
|
|
904
1511
|
}
|
|
1512
|
+
async updateCameraInfoWithAutofocus() {
|
|
1513
|
+
try {
|
|
1514
|
+
const cameraInfo = await this.cameraService.getCameraInfo();
|
|
1515
|
+
this.cameraInfoWithAutofocus = cameraInfo;
|
|
1516
|
+
}
|
|
1517
|
+
catch (error) {
|
|
1518
|
+
// Keep existing state if update fails
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
905
1521
|
async handleCameraSwitch(cameraId) {
|
|
906
1522
|
if (this.isSwitchingCamera)
|
|
907
1523
|
return; // Prevent multiple simultaneous switches
|
|
908
1524
|
try {
|
|
909
1525
|
// Close the selector immediately when user selects a camera
|
|
910
1526
|
this.showCameraSelector = false;
|
|
1527
|
+
// Check if user selected the same camera that's already active
|
|
1528
|
+
const currentCameraId = this.cameraService.getSelectedCameraId();
|
|
1529
|
+
if (currentCameraId === cameraId) {
|
|
1530
|
+
// Same camera selected, just close the selector without switching
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
911
1533
|
this.isSwitchingCamera = true;
|
|
912
|
-
this.logger.state('INICIANDO_CAMBIO_CAMARA', {
|
|
913
|
-
from: this.cameraService.getSelectedCameraId(),
|
|
914
|
-
to: cameraId
|
|
915
|
-
});
|
|
916
1534
|
// Stop current video stream
|
|
917
1535
|
if (this.videoStream) {
|
|
918
1536
|
this.videoStream.getTracks().forEach(track => track.stop());
|
|
@@ -923,21 +1541,17 @@ export class JaakStamps {
|
|
|
923
1541
|
const newStream = await this.cameraService.setupCamera();
|
|
924
1542
|
// Update video element
|
|
925
1543
|
await this.initializeVideoStream(newStream);
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
isRearCamera: this.cameraService.isRearCamera(newStream)
|
|
929
|
-
});
|
|
1544
|
+
// Update camera info with autofocus data
|
|
1545
|
+
await this.updateCameraInfoWithAutofocus();
|
|
930
1546
|
}
|
|
931
1547
|
catch (error) {
|
|
932
|
-
this.logger.error('Error al cambiar cámara:', error);
|
|
933
1548
|
// Try to restore previous camera if switch failed
|
|
934
1549
|
try {
|
|
935
1550
|
const fallbackStream = await this.cameraService.setupCamera();
|
|
936
1551
|
await this.initializeVideoStream(fallbackStream);
|
|
937
1552
|
}
|
|
938
1553
|
catch (fallbackError) {
|
|
939
|
-
this.
|
|
940
|
-
this.updateStatus('Error al cambiar cámara', 'No se pudo completar el cambio de dispositivo', 'error');
|
|
1554
|
+
this.updateStatus('Error al cambiar cámara', 'No se pudo cambiar el dispositivo', 'error');
|
|
941
1555
|
}
|
|
942
1556
|
}
|
|
943
1557
|
finally {
|
|
@@ -959,6 +1573,13 @@ export class JaakStamps {
|
|
|
959
1573
|
this.detectionBoxes = [];
|
|
960
1574
|
this.alignmentStartTime = undefined;
|
|
961
1575
|
this.hasDocumentDetected = false;
|
|
1576
|
+
// Reset sistema simplificado
|
|
1577
|
+
this.processedFramesCount = 0;
|
|
1578
|
+
this.slowFrameCount = 0;
|
|
1579
|
+
// Reset performance degradation state
|
|
1580
|
+
this.hasAutoSwitchedToManual = false;
|
|
1581
|
+
this.performanceDegradedMode = false;
|
|
1582
|
+
this.showPerformanceMessage = false;
|
|
962
1583
|
if (this.alignmentTimer) {
|
|
963
1584
|
clearTimeout(this.alignmentTimer);
|
|
964
1585
|
this.alignmentTimer = undefined;
|
|
@@ -978,7 +1599,7 @@ export class JaakStamps {
|
|
|
978
1599
|
this.stateManager.updateCaptureState({ isVideoActive: false, isLoading: false });
|
|
979
1600
|
}
|
|
980
1601
|
this.isMaskReady = false;
|
|
981
|
-
this.updateStatus('
|
|
1602
|
+
this.updateStatus('Captura finalizada', '', 'ready');
|
|
982
1603
|
this.detectionBoxes = [];
|
|
983
1604
|
this.cleanup();
|
|
984
1605
|
}
|
|
@@ -989,7 +1610,6 @@ export class JaakStamps {
|
|
|
989
1610
|
this.performanceUpdateInterval = window.setInterval(() => {
|
|
990
1611
|
this.updatePerformanceMetrics();
|
|
991
1612
|
}, 500);
|
|
992
|
-
this.logger.debug('Monitor de performance inicializado');
|
|
993
1613
|
}
|
|
994
1614
|
updatePerformanceMetrics() {
|
|
995
1615
|
const currentTime = performance.now();
|
|
@@ -1032,6 +1652,84 @@ export class JaakStamps {
|
|
|
1032
1652
|
if (detectionsFound > 0) {
|
|
1033
1653
|
this.performanceMetrics.successfulDetections++;
|
|
1034
1654
|
}
|
|
1655
|
+
// Update performance history for adaptive frame skipping
|
|
1656
|
+
this.performanceHistory.push(processingTime);
|
|
1657
|
+
if (this.performanceHistory.length > this.PERFORMANCE_HISTORY_SIZE) {
|
|
1658
|
+
this.performanceHistory.shift();
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
// Adaptive frame skipping based on performance
|
|
1662
|
+
getAdaptiveFrameSkip() {
|
|
1663
|
+
// Base conditions for high frame skip
|
|
1664
|
+
if (this.consecutiveFailures > 20)
|
|
1665
|
+
return this.MAX_FRAME_SKIP;
|
|
1666
|
+
if (this.performanceMetrics.inferenceTime > 200)
|
|
1667
|
+
return 6;
|
|
1668
|
+
if (this.performanceMetrics.memoryUsage > 200)
|
|
1669
|
+
return 5;
|
|
1670
|
+
// Calculate average processing time
|
|
1671
|
+
if (this.performanceHistory.length > 0) {
|
|
1672
|
+
const avgProcessingTime = this.performanceHistory.reduce((a, b) => a + b, 0) / this.performanceHistory.length;
|
|
1673
|
+
if (avgProcessingTime > 100)
|
|
1674
|
+
return 4;
|
|
1675
|
+
if (avgProcessingTime > 80)
|
|
1676
|
+
return 3;
|
|
1677
|
+
if (avgProcessingTime > 60)
|
|
1678
|
+
return this.BASE_FRAME_SKIP + 1;
|
|
1679
|
+
}
|
|
1680
|
+
// Low memory devices get higher frame skip
|
|
1681
|
+
if (this.performanceMetrics.memoryUsage > 150)
|
|
1682
|
+
return 4;
|
|
1683
|
+
// Performance is good, use base frame skip
|
|
1684
|
+
return this.BASE_FRAME_SKIP;
|
|
1685
|
+
}
|
|
1686
|
+
// Método para resetear la máscara a color blanco
|
|
1687
|
+
resetMaskToWhite() {
|
|
1688
|
+
const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
|
|
1689
|
+
const corners = this.el.shadowRoot?.querySelectorAll('.corner');
|
|
1690
|
+
const topSide = this.el.shadowRoot?.querySelector('.side-top');
|
|
1691
|
+
const rightSide = this.el.shadowRoot?.querySelector('.side-right');
|
|
1692
|
+
const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
|
|
1693
|
+
const leftSide = this.el.shadowRoot?.querySelector('.side-left');
|
|
1694
|
+
// Remover todas las clases de alineación y estado perfecto
|
|
1695
|
+
cardOutline?.classList.remove('perfect-match');
|
|
1696
|
+
corners?.forEach(corner => corner.classList.remove('perfect-match'));
|
|
1697
|
+
topSide?.classList.remove('aligned');
|
|
1698
|
+
rightSide?.classList.remove('aligned');
|
|
1699
|
+
bottomSide?.classList.remove('aligned');
|
|
1700
|
+
leftSide?.classList.remove('aligned');
|
|
1701
|
+
// Resetear estado de alineación
|
|
1702
|
+
this.sideAlignment = { top: false, right: false, bottom: false, left: false };
|
|
1703
|
+
}
|
|
1704
|
+
// Canvas pool management for screenshots
|
|
1705
|
+
getPooledCanvas(width, height) {
|
|
1706
|
+
// Try to find a canvas with matching dimensions
|
|
1707
|
+
const matchingIndex = this.canvasPool.findIndex(canvas => canvas.width === width && canvas.height === height);
|
|
1708
|
+
if (matchingIndex !== -1) {
|
|
1709
|
+
return this.canvasPool.splice(matchingIndex, 1)[0];
|
|
1710
|
+
}
|
|
1711
|
+
// If no matching canvas, try to reuse any canvas and resize
|
|
1712
|
+
if (this.canvasPool.length > 0) {
|
|
1713
|
+
const canvas = this.canvasPool.pop();
|
|
1714
|
+
canvas.width = width;
|
|
1715
|
+
canvas.height = height;
|
|
1716
|
+
return canvas;
|
|
1717
|
+
}
|
|
1718
|
+
// Create new canvas if pool is empty
|
|
1719
|
+
const canvas = document.createElement('canvas');
|
|
1720
|
+
canvas.width = width;
|
|
1721
|
+
canvas.height = height;
|
|
1722
|
+
return canvas;
|
|
1723
|
+
}
|
|
1724
|
+
returnCanvasToPool(canvas) {
|
|
1725
|
+
// Only return to pool if not at max capacity
|
|
1726
|
+
if (this.canvasPool.length < this.MAX_CANVAS_POOL_SIZE) {
|
|
1727
|
+
// Clear the canvas before returning to pool
|
|
1728
|
+
const ctx = canvas.getContext('2d');
|
|
1729
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1730
|
+
this.canvasPool.push(canvas);
|
|
1731
|
+
}
|
|
1732
|
+
// If pool is full, let the canvas be garbage collected
|
|
1035
1733
|
}
|
|
1036
1734
|
static get is() { return "jaak-stamps"; }
|
|
1037
1735
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1147,6 +1845,26 @@ export class JaakStamps {
|
|
|
1147
1845
|
"reflect": false,
|
|
1148
1846
|
"defaultValue": "false"
|
|
1149
1847
|
},
|
|
1848
|
+
"useDocumentDetector": {
|
|
1849
|
+
"type": "boolean",
|
|
1850
|
+
"attribute": "use-document-detector",
|
|
1851
|
+
"mutable": false,
|
|
1852
|
+
"complexType": {
|
|
1853
|
+
"original": "boolean",
|
|
1854
|
+
"resolved": "boolean",
|
|
1855
|
+
"references": {}
|
|
1856
|
+
},
|
|
1857
|
+
"required": false,
|
|
1858
|
+
"optional": false,
|
|
1859
|
+
"docs": {
|
|
1860
|
+
"tags": [],
|
|
1861
|
+
"text": ""
|
|
1862
|
+
},
|
|
1863
|
+
"getter": false,
|
|
1864
|
+
"setter": false,
|
|
1865
|
+
"reflect": false,
|
|
1866
|
+
"defaultValue": "true"
|
|
1867
|
+
},
|
|
1150
1868
|
"preferredCamera": {
|
|
1151
1869
|
"type": "string",
|
|
1152
1870
|
"attribute": "preferred-camera",
|
|
@@ -1238,9 +1956,15 @@ export class JaakStamps {
|
|
|
1238
1956
|
"showCameraSelector": {},
|
|
1239
1957
|
"isSwitchingCamera": {},
|
|
1240
1958
|
"hasDocumentDetected": {},
|
|
1959
|
+
"cameraInfoWithAutofocus": {},
|
|
1241
1960
|
"currentStatus": {},
|
|
1242
1961
|
"performanceData": {},
|
|
1243
|
-
"backDocumentTimerRemaining": {}
|
|
1962
|
+
"backDocumentTimerRemaining": {},
|
|
1963
|
+
"showManualCaptureButton": {},
|
|
1964
|
+
"performanceDegradedMode": {},
|
|
1965
|
+
"showPerformanceMessage": {},
|
|
1966
|
+
"captureStateVersion": {},
|
|
1967
|
+
"processingButton": {}
|
|
1244
1968
|
};
|
|
1245
1969
|
}
|
|
1246
1970
|
static get events() {
|
|
@@ -1319,7 +2043,7 @@ export class JaakStamps {
|
|
|
1319
2043
|
},
|
|
1320
2044
|
"startCapture": {
|
|
1321
2045
|
"complexType": {
|
|
1322
|
-
"signature": "() => Promise<
|
|
2046
|
+
"signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
|
|
1323
2047
|
"parameters": [],
|
|
1324
2048
|
"references": {
|
|
1325
2049
|
"Promise": {
|
|
@@ -1327,7 +2051,7 @@ export class JaakStamps {
|
|
|
1327
2051
|
"id": "global::Promise"
|
|
1328
2052
|
}
|
|
1329
2053
|
},
|
|
1330
|
-
"return": "Promise<
|
|
2054
|
+
"return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
|
|
1331
2055
|
},
|
|
1332
2056
|
"docs": {
|
|
1333
2057
|
"text": "",
|
|
@@ -1336,7 +2060,7 @@ export class JaakStamps {
|
|
|
1336
2060
|
},
|
|
1337
2061
|
"stopCapture": {
|
|
1338
2062
|
"complexType": {
|
|
1339
|
-
"signature": "() => Promise<
|
|
2063
|
+
"signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
|
|
1340
2064
|
"parameters": [],
|
|
1341
2065
|
"references": {
|
|
1342
2066
|
"Promise": {
|
|
@@ -1344,7 +2068,7 @@ export class JaakStamps {
|
|
|
1344
2068
|
"id": "global::Promise"
|
|
1345
2069
|
}
|
|
1346
2070
|
},
|
|
1347
|
-
"return": "Promise<
|
|
2071
|
+
"return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
|
|
1348
2072
|
},
|
|
1349
2073
|
"docs": {
|
|
1350
2074
|
"text": "",
|
|
@@ -1353,7 +2077,7 @@ export class JaakStamps {
|
|
|
1353
2077
|
},
|
|
1354
2078
|
"resetCapture": {
|
|
1355
2079
|
"complexType": {
|
|
1356
|
-
"signature": "() => Promise<
|
|
2080
|
+
"signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
|
|
1357
2081
|
"parameters": [],
|
|
1358
2082
|
"references": {
|
|
1359
2083
|
"Promise": {
|
|
@@ -1361,7 +2085,7 @@ export class JaakStamps {
|
|
|
1361
2085
|
"id": "global::Promise"
|
|
1362
2086
|
}
|
|
1363
2087
|
},
|
|
1364
|
-
"return": "Promise<
|
|
2088
|
+
"return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
|
|
1365
2089
|
},
|
|
1366
2090
|
"docs": {
|
|
1367
2091
|
"text": "",
|
|
@@ -1370,7 +2094,7 @@ export class JaakStamps {
|
|
|
1370
2094
|
},
|
|
1371
2095
|
"skipBackCapture": {
|
|
1372
2096
|
"complexType": {
|
|
1373
|
-
"signature": "() => Promise<
|
|
2097
|
+
"signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
|
|
1374
2098
|
"parameters": [],
|
|
1375
2099
|
"references": {
|
|
1376
2100
|
"Promise": {
|
|
@@ -1378,7 +2102,7 @@ export class JaakStamps {
|
|
|
1378
2102
|
"id": "global::Promise"
|
|
1379
2103
|
}
|
|
1380
2104
|
},
|
|
1381
|
-
"return": "Promise<
|
|
2105
|
+
"return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
|
|
1382
2106
|
},
|
|
1383
2107
|
"docs": {
|
|
1384
2108
|
"text": "",
|
|
@@ -1448,7 +2172,7 @@ export class JaakStamps {
|
|
|
1448
2172
|
},
|
|
1449
2173
|
"setPreferredCamera": {
|
|
1450
2174
|
"complexType": {
|
|
1451
|
-
"signature": "(camera: \"auto\" | \"front\" | \"back\") => Promise<{ success: boolean; selectedCamera: string; availableCameras: number; }>",
|
|
2175
|
+
"signature": "(camera: \"auto\" | \"front\" | \"back\") => Promise<{ success: boolean; selectedCamera: string; availableCameras: number; error?: undefined; } | { success: boolean; error: any; selectedCamera: any; availableCameras: number; }>",
|
|
1452
2176
|
"parameters": [{
|
|
1453
2177
|
"name": "camera",
|
|
1454
2178
|
"type": "\"auto\" | \"front\" | \"back\"",
|
|
@@ -1460,7 +2184,7 @@ export class JaakStamps {
|
|
|
1460
2184
|
"id": "global::Promise"
|
|
1461
2185
|
}
|
|
1462
2186
|
},
|
|
1463
|
-
"return": "Promise<{ success: boolean; selectedCamera: string; availableCameras: number; }>"
|
|
2187
|
+
"return": "Promise<{ success: boolean; selectedCamera: string; availableCameras: number; error?: undefined; } | { success: boolean; error: any; selectedCamera: any; availableCameras: number; }>"
|
|
1464
2188
|
},
|
|
1465
2189
|
"docs": {
|
|
1466
2190
|
"text": "",
|
|
@@ -1469,7 +2193,7 @@ export class JaakStamps {
|
|
|
1469
2193
|
},
|
|
1470
2194
|
"setCaptureDelay": {
|
|
1471
2195
|
"complexType": {
|
|
1472
|
-
"signature": "(delay: number) => Promise<{ success: boolean; captureDelay: number; }>",
|
|
2196
|
+
"signature": "(delay: number) => Promise<{ success: boolean; error: string; captureDelay: number; } | { success: boolean; captureDelay: number; error?: undefined; }>",
|
|
1473
2197
|
"parameters": [{
|
|
1474
2198
|
"name": "delay",
|
|
1475
2199
|
"type": "number",
|
|
@@ -1481,7 +2205,7 @@ export class JaakStamps {
|
|
|
1481
2205
|
"id": "global::Promise"
|
|
1482
2206
|
}
|
|
1483
2207
|
},
|
|
1484
|
-
"return": "Promise<{ success: boolean; captureDelay: number; }>"
|
|
2208
|
+
"return": "Promise<{ success: boolean; error: string; captureDelay: number; } | { success: boolean; captureDelay: number; error?: undefined; }>"
|
|
1485
2209
|
},
|
|
1486
2210
|
"docs": {
|
|
1487
2211
|
"text": "",
|
|
@@ -1504,52 +2228,6 @@ export class JaakStamps {
|
|
|
1504
2228
|
"text": "",
|
|
1505
2229
|
"tags": []
|
|
1506
2230
|
}
|
|
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
2231
|
}
|
|
1554
2232
|
};
|
|
1555
2233
|
}
|