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