@jaak.ai/stamps 2.0.0 → 2.1.0-dev.1
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 +546 -611
- 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 +346 -237
- 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 +548 -614
- 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 +546 -611
- 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-2264b5b4.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js.map +0 -1
- package/dist/types/services/LoggerService.d.ts +0 -12
- package/dist/types/services/interfaces/ILogger.d.ts +0 -8
|
@@ -2,46 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
var index = require('./index-BfhtOB0D.js');
|
|
4
4
|
|
|
5
|
-
class LoggerService {
|
|
6
|
-
debugMode;
|
|
7
|
-
constructor(debug = false) {
|
|
8
|
-
this.debugMode = debug;
|
|
9
|
-
}
|
|
10
|
-
setDebugMode(debug) {
|
|
11
|
-
this.debugMode = debug;
|
|
12
|
-
}
|
|
13
|
-
info(...args) {
|
|
14
|
-
if (this.debugMode) {
|
|
15
|
-
console.log(`[JAAK-STAMPS] [INFO] [${new Date().toLocaleTimeString()}]`, ...args);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
warn(...args) {
|
|
19
|
-
if (this.debugMode) {
|
|
20
|
-
console.warn(`[JAAK-STAMPS] [WARN] [${new Date().toLocaleTimeString()}]`, ...args);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
error(...args) {
|
|
24
|
-
if (this.debugMode) {
|
|
25
|
-
console.error(`[JAAK-STAMPS] [ERROR] [${new Date().toLocaleTimeString()}]`, ...args);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
debug(...args) {
|
|
29
|
-
if (this.debugMode) {
|
|
30
|
-
console.debug(`[JAAK-STAMPS] [DEBUG] [${new Date().toLocaleTimeString()}]`, ...args);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
state(state, data) {
|
|
34
|
-
if (this.debugMode) {
|
|
35
|
-
console.log(`[JAAK-STAMPS] [STATE] [${new Date().toLocaleTimeString()}] ${state}`, data || '');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
performance(operation, duration) {
|
|
39
|
-
if (this.debugMode) {
|
|
40
|
-
console.log(`[JAAK-STAMPS] [PERF] [${new Date().toLocaleTimeString()}] ${operation}: ${duration}ms`);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
5
|
class EventBusService {
|
|
46
6
|
events = new Map();
|
|
47
7
|
on(event, callback) {
|
|
@@ -67,7 +27,7 @@ class EventBusService {
|
|
|
67
27
|
callback(data);
|
|
68
28
|
}
|
|
69
29
|
catch (error) {
|
|
70
|
-
|
|
30
|
+
// Silently handle callback errors
|
|
71
31
|
}
|
|
72
32
|
});
|
|
73
33
|
}
|
|
@@ -194,16 +154,20 @@ class StateManagerService {
|
|
|
194
154
|
}
|
|
195
155
|
|
|
196
156
|
class CameraService {
|
|
197
|
-
logger;
|
|
198
157
|
eventBus;
|
|
199
158
|
availableCameras = [];
|
|
200
159
|
selectedCameraId = null;
|
|
201
160
|
deviceType = 'desktop';
|
|
202
161
|
preferredCameraFacing = null;
|
|
203
162
|
preferredCamera = 'auto';
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
163
|
+
// Optimization: Static caches for performance
|
|
164
|
+
static cameraCapabilitiesCache = new Map();
|
|
165
|
+
static deviceEnumerationCache = null;
|
|
166
|
+
static CACHE_DURATION = 30000; // 30 seconds
|
|
167
|
+
// Stream management for optimization
|
|
168
|
+
currentStreamPromise;
|
|
169
|
+
lastStreamConstraints;
|
|
170
|
+
constructor(eventBus, preferredCamera = 'auto') {
|
|
207
171
|
this.eventBus = eventBus;
|
|
208
172
|
this.preferredCamera = preferredCamera;
|
|
209
173
|
}
|
|
@@ -220,18 +184,21 @@ class CameraService {
|
|
|
220
184
|
else {
|
|
221
185
|
this.deviceType = 'desktop';
|
|
222
186
|
}
|
|
223
|
-
this.logger.state('DISPOSITIVO_DETECTADO', {
|
|
224
|
-
deviceType: this.deviceType,
|
|
225
|
-
userAgent: navigator.userAgent,
|
|
226
|
-
screenDimensions: { width: window.innerWidth, height: window.innerHeight }
|
|
227
|
-
});
|
|
228
187
|
return this.deviceType;
|
|
229
188
|
}
|
|
230
189
|
async enumerateDevices() {
|
|
231
190
|
try {
|
|
191
|
+
// Check cache first for optimization
|
|
192
|
+
const now = Date.now();
|
|
193
|
+
if (CameraService.deviceEnumerationCache &&
|
|
194
|
+
(now - CameraService.deviceEnumerationCache.timestamp) < CameraService.CACHE_DURATION) {
|
|
195
|
+
this.availableCameras = CameraService.deviceEnumerationCache.devices;
|
|
196
|
+
await this.setInitialCameraPreference();
|
|
197
|
+
this.eventBus.emit('camera-changed', this.selectedCameraId);
|
|
198
|
+
return this.availableCameras;
|
|
199
|
+
}
|
|
232
200
|
const permissionStatus = await this.checkCameraPermission();
|
|
233
201
|
if (permissionStatus === 'denied') {
|
|
234
|
-
this.logger.error('Permiso de cámara denegado por el usuario');
|
|
235
202
|
return [];
|
|
236
203
|
}
|
|
237
204
|
if (permissionStatus === 'prompt') {
|
|
@@ -240,24 +207,16 @@ class CameraService {
|
|
|
240
207
|
}
|
|
241
208
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
242
209
|
this.availableCameras = devices.filter(device => device.kind === 'videoinput');
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
});
|
|
250
|
-
this.setInitialCameraPreference();
|
|
251
|
-
this.logger.state('PREFERENCIA_INICIAL_APLICADA', {
|
|
252
|
-
selectedCameraId: this.selectedCameraId,
|
|
253
|
-
preferredCameraFacing: this.preferredCameraFacing,
|
|
254
|
-
preferredCamera: this.preferredCamera
|
|
255
|
-
});
|
|
210
|
+
// Cache the results for optimization
|
|
211
|
+
CameraService.deviceEnumerationCache = {
|
|
212
|
+
devices: this.availableCameras,
|
|
213
|
+
timestamp: now
|
|
214
|
+
};
|
|
215
|
+
await this.setInitialCameraPreference();
|
|
256
216
|
this.eventBus.emit('camera-changed', this.selectedCameraId);
|
|
257
217
|
return this.availableCameras;
|
|
258
218
|
}
|
|
259
219
|
catch (error) {
|
|
260
|
-
this.logger.error('Error al enumerar cámaras disponibles:', error);
|
|
261
220
|
this.handleCameraPermissionError(error);
|
|
262
221
|
return [];
|
|
263
222
|
}
|
|
@@ -278,8 +237,6 @@ class CameraService {
|
|
|
278
237
|
}
|
|
279
238
|
this.selectedCameraId = cameraId;
|
|
280
239
|
this.updatePreferredFacing(camera);
|
|
281
|
-
this.isManuallySelected = true; // Mark as manually selected
|
|
282
|
-
this.savePreference();
|
|
283
240
|
this.eventBus.emit('camera-changed', cameraId);
|
|
284
241
|
}
|
|
285
242
|
getPreferredFacing() {
|
|
@@ -288,14 +245,20 @@ class CameraService {
|
|
|
288
245
|
async setupCamera(constraints) {
|
|
289
246
|
try {
|
|
290
247
|
const finalConstraints = constraints || await this.getMaxResolution();
|
|
291
|
-
|
|
248
|
+
// Optimization: Check if we can reuse existing stream with same constraints
|
|
249
|
+
if (this.currentStreamPromise && this.constraintsEqual(finalConstraints, this.lastStreamConstraints)) {
|
|
250
|
+
return await this.currentStreamPromise;
|
|
251
|
+
}
|
|
252
|
+
// Create new stream promise for lazy loading
|
|
253
|
+
this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
|
|
292
254
|
video: finalConstraints,
|
|
293
255
|
audio: false
|
|
294
256
|
});
|
|
257
|
+
this.lastStreamConstraints = finalConstraints;
|
|
258
|
+
const stream = await this.currentStreamPromise;
|
|
295
259
|
return stream;
|
|
296
260
|
}
|
|
297
261
|
catch (error) {
|
|
298
|
-
this.logger.error('Error en setupCamera, intentando con restricciones básicas:', error);
|
|
299
262
|
// Fallback to basic constraints if getMaxResolution fails
|
|
300
263
|
const basicConstraints = {
|
|
301
264
|
width: { ideal: 1280 },
|
|
@@ -305,65 +268,21 @@ class CameraService {
|
|
|
305
268
|
if (this.deviceType !== 'desktop' && this.preferredCameraFacing) {
|
|
306
269
|
basicConstraints.facingMode = this.preferredCameraFacing;
|
|
307
270
|
}
|
|
308
|
-
this.
|
|
309
|
-
return await navigator.mediaDevices.getUserMedia({
|
|
271
|
+
this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
|
|
310
272
|
video: basicConstraints,
|
|
311
273
|
audio: false
|
|
312
274
|
});
|
|
275
|
+
this.lastStreamConstraints = basicConstraints;
|
|
276
|
+
return await this.currentStreamPromise;
|
|
313
277
|
}
|
|
314
278
|
}
|
|
315
279
|
async switchCamera(cameraId) {
|
|
316
280
|
const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
|
|
317
281
|
if (!selectedCamera) {
|
|
318
|
-
this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
|
|
319
282
|
await this.enumerateDevices();
|
|
320
283
|
return;
|
|
321
284
|
}
|
|
322
285
|
await this.setSelectedCamera(cameraId);
|
|
323
|
-
this.logger.state('CAMARA_CAMBIADA', {
|
|
324
|
-
label: selectedCamera.label,
|
|
325
|
-
deviceId: selectedCamera.deviceId,
|
|
326
|
-
isManuallySelected: this.isManuallySelected
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
async flipToNextCamera() {
|
|
330
|
-
if (!this.isMultipleCamerasAvailable())
|
|
331
|
-
return;
|
|
332
|
-
const currentIndex = this.availableCameras.findIndex(camera => camera.deviceId === this.selectedCameraId);
|
|
333
|
-
const nextIndex = (currentIndex + 1) % this.availableCameras.length;
|
|
334
|
-
const nextCamera = this.availableCameras[nextIndex];
|
|
335
|
-
await this.switchCamera(nextCamera.deviceId);
|
|
336
|
-
}
|
|
337
|
-
savePreference() {
|
|
338
|
-
try {
|
|
339
|
-
const preference = {
|
|
340
|
-
cameraId: this.selectedCameraId,
|
|
341
|
-
facing: this.preferredCameraFacing,
|
|
342
|
-
timestamp: Date.now()
|
|
343
|
-
};
|
|
344
|
-
localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
|
|
345
|
-
this.logger.state('PREFERENCIA_CAMARA_GUARDADA', preference);
|
|
346
|
-
}
|
|
347
|
-
catch (error) {
|
|
348
|
-
this.logger.warn('Error al guardar preferencia de cámara:', error);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
loadPreference() {
|
|
352
|
-
try {
|
|
353
|
-
const saved = localStorage.getItem('jaak-stamps-camera-preference');
|
|
354
|
-
if (saved) {
|
|
355
|
-
const preference = JSON.parse(saved);
|
|
356
|
-
const isStillAvailable = this.availableCameras.some(camera => camera.deviceId === preference.cameraId);
|
|
357
|
-
if (isStillAvailable) {
|
|
358
|
-
this.selectedCameraId = preference.cameraId;
|
|
359
|
-
this.preferredCameraFacing = preference.facing;
|
|
360
|
-
this.logger.state('PREFERENCIA_CAMARA_CARGADA', preference);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
catch (error) {
|
|
365
|
-
this.logger.warn('Error al cargar preferencia de cámara:', error);
|
|
366
|
-
}
|
|
367
286
|
}
|
|
368
287
|
isRearCamera(stream) {
|
|
369
288
|
const videoTrack = stream.getVideoTracks()[0];
|
|
@@ -372,13 +291,18 @@ class CameraService {
|
|
|
372
291
|
const settings = videoTrack.getSettings();
|
|
373
292
|
return settings.facingMode === 'environment';
|
|
374
293
|
}
|
|
375
|
-
getCameraInfo() {
|
|
376
|
-
|
|
377
|
-
|
|
294
|
+
async getCameraInfo() {
|
|
295
|
+
const availableCamerasWithAutofocus = await Promise.all(this.availableCameras.map(async (camera) => {
|
|
296
|
+
const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
|
|
297
|
+
return {
|
|
378
298
|
id: camera.deviceId,
|
|
379
299
|
label: camera.label || 'Unknown Camera',
|
|
380
|
-
selected: camera.deviceId === this.selectedCameraId
|
|
381
|
-
|
|
300
|
+
selected: camera.deviceId === this.selectedCameraId,
|
|
301
|
+
hasAutofocus
|
|
302
|
+
};
|
|
303
|
+
}));
|
|
304
|
+
return {
|
|
305
|
+
availableCameras: availableCamerasWithAutofocus,
|
|
382
306
|
selectedCameraId: this.selectedCameraId,
|
|
383
307
|
deviceType: this.deviceType,
|
|
384
308
|
isMultipleCamerasAvailable: this.isMultipleCamerasAvailable(),
|
|
@@ -394,7 +318,6 @@ class CameraService {
|
|
|
394
318
|
return permission.state;
|
|
395
319
|
}
|
|
396
320
|
catch (error) {
|
|
397
|
-
this.logger.warn('No se pudo verificar permisos de cámara:', error);
|
|
398
321
|
return 'prompt';
|
|
399
322
|
}
|
|
400
323
|
}
|
|
@@ -402,35 +325,48 @@ class CameraService {
|
|
|
402
325
|
this.availableCameras = [];
|
|
403
326
|
this.eventBus.emit('error', new Error(`Camera permission error: ${error.message}`));
|
|
404
327
|
}
|
|
405
|
-
setInitialCameraPreference() {
|
|
328
|
+
async setInitialCameraPreference() {
|
|
406
329
|
if (this.availableCameras.length === 0)
|
|
407
330
|
return;
|
|
408
|
-
// Clear any existing localStorage preferences to ensure fresh start
|
|
409
|
-
localStorage.removeItem('jaak-stamps-camera-preference');
|
|
410
|
-
this.logger.state('APLICANDO_PREFERENCIA_INICIAL', {
|
|
411
|
-
preferredCamera: this.preferredCamera,
|
|
412
|
-
availableCamerasCount: this.availableCameras.length
|
|
413
|
-
});
|
|
414
|
-
this.isManuallySelected = false; // Reset manual selection flag
|
|
415
331
|
if (this.preferredCamera === 'front') {
|
|
416
|
-
this.selectFrontCamera();
|
|
332
|
+
await this.selectFrontCamera();
|
|
417
333
|
}
|
|
418
334
|
else if (this.preferredCamera === 'back') {
|
|
419
|
-
this.selectBackCamera();
|
|
335
|
+
await this.selectBackCamera();
|
|
420
336
|
}
|
|
421
337
|
else {
|
|
422
|
-
this.selectAutoCamera();
|
|
338
|
+
await this.selectAutoCamera();
|
|
423
339
|
}
|
|
424
340
|
}
|
|
425
|
-
|
|
341
|
+
async checkCameraAutofocus(deviceId) {
|
|
342
|
+
try {
|
|
343
|
+
// Check cache first for optimization
|
|
344
|
+
if (CameraService.cameraCapabilitiesCache.has(deviceId)) {
|
|
345
|
+
const cached = CameraService.cameraCapabilitiesCache.get(deviceId);
|
|
346
|
+
return typeof cached === 'boolean' ? cached : false;
|
|
347
|
+
}
|
|
348
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
349
|
+
video: { deviceId: { exact: deviceId } }
|
|
350
|
+
});
|
|
351
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
352
|
+
const capabilities = videoTrack.getCapabilities();
|
|
353
|
+
stream.getTracks().forEach(track => track.stop());
|
|
354
|
+
const hasFocusMode = capabilities.focusMode && Array.isArray(capabilities.focusMode) && capabilities.focusMode.length > 0;
|
|
355
|
+
const hasAutofocus = hasFocusMode && (capabilities.focusMode.includes('continuous') ||
|
|
356
|
+
capabilities.focusMode.includes('auto'));
|
|
357
|
+
// Cache the result for future use
|
|
358
|
+
CameraService.cameraCapabilitiesCache.set(deviceId, hasAutofocus);
|
|
359
|
+
return hasAutofocus;
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
// Cache negative result to avoid repeated failures
|
|
363
|
+
CameraService.cameraCapabilitiesCache.set(deviceId, false);
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async selectFrontCamera() {
|
|
426
368
|
this.preferredCameraFacing = 'user';
|
|
427
|
-
this.
|
|
428
|
-
availableCameras: this.availableCameras.map(cam => ({
|
|
429
|
-
id: cam.deviceId,
|
|
430
|
-
label: cam.label
|
|
431
|
-
}))
|
|
432
|
-
});
|
|
433
|
-
const frontCamera = this.availableCameras.find(camera => {
|
|
369
|
+
const frontCameras = this.availableCameras.filter(camera => {
|
|
434
370
|
const label = camera.label.toLowerCase();
|
|
435
371
|
return label.includes('front') ||
|
|
436
372
|
label.includes('user') ||
|
|
@@ -438,24 +374,24 @@ class CameraService {
|
|
|
438
374
|
label.includes('frontal') ||
|
|
439
375
|
(!label.includes('back') && !label.includes('rear') && !label.includes('environment'));
|
|
440
376
|
});
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
377
|
+
let selectedCamera = null;
|
|
378
|
+
if (frontCameras.length > 0) {
|
|
379
|
+
for (const camera of frontCameras) {
|
|
380
|
+
const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
|
|
381
|
+
if (hasAutofocus) {
|
|
382
|
+
selectedCamera = camera;
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (!selectedCamera) {
|
|
387
|
+
selectedCamera = frontCameras[0];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
|
|
448
391
|
}
|
|
449
|
-
selectBackCamera() {
|
|
392
|
+
async selectBackCamera() {
|
|
450
393
|
this.preferredCameraFacing = 'environment';
|
|
451
|
-
this.
|
|
452
|
-
availableCameras: this.availableCameras.map(cam => ({
|
|
453
|
-
id: cam.deviceId,
|
|
454
|
-
label: cam.label
|
|
455
|
-
}))
|
|
456
|
-
});
|
|
457
|
-
// Try to find rear camera with multiple detection methods
|
|
458
|
-
let backCamera = this.availableCameras.find(camera => {
|
|
394
|
+
const backCameras = this.availableCameras.filter(camera => {
|
|
459
395
|
const label = camera.label.toLowerCase();
|
|
460
396
|
return label.includes('back') ||
|
|
461
397
|
label.includes('rear') ||
|
|
@@ -463,34 +399,33 @@ class CameraService {
|
|
|
463
399
|
label.includes('trasera') ||
|
|
464
400
|
label.includes('posterior');
|
|
465
401
|
});
|
|
466
|
-
|
|
467
|
-
if (
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
402
|
+
let selectedCamera = null;
|
|
403
|
+
if (backCameras.length > 0) {
|
|
404
|
+
for (const camera of backCameras) {
|
|
405
|
+
const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
|
|
406
|
+
if (hasAutofocus) {
|
|
407
|
+
selectedCamera = camera;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (!selectedCamera) {
|
|
412
|
+
selectedCamera = backCameras[0];
|
|
413
|
+
}
|
|
473
414
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
415
|
+
else if (this.availableCameras.length > 1) {
|
|
416
|
+
const lastCamera = this.availableCameras[this.availableCameras.length - 1];
|
|
417
|
+
const hasAutofocus = await this.checkCameraAutofocus(lastCamera.deviceId);
|
|
418
|
+
selectedCamera = hasAutofocus ? lastCamera : this.availableCameras[this.availableCameras.length - 1];
|
|
419
|
+
}
|
|
420
|
+
this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
|
|
480
421
|
}
|
|
481
|
-
selectAutoCamera() {
|
|
422
|
+
async selectAutoCamera() {
|
|
482
423
|
if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
|
|
483
|
-
this.selectBackCamera();
|
|
484
|
-
this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
|
|
424
|
+
await this.selectBackCamera();
|
|
485
425
|
}
|
|
486
426
|
else {
|
|
487
427
|
// For desktop, prefer front camera (usually built-in webcam)
|
|
488
|
-
this.selectFrontCamera();
|
|
489
|
-
this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', {
|
|
490
|
-
type: 'front',
|
|
491
|
-
label: this.availableCameras[0].label,
|
|
492
|
-
deviceId: this.selectedCameraId
|
|
493
|
-
});
|
|
428
|
+
await this.selectFrontCamera();
|
|
494
429
|
}
|
|
495
430
|
}
|
|
496
431
|
updatePreferredFacing(camera) {
|
|
@@ -506,40 +441,19 @@ class CameraService {
|
|
|
506
441
|
await this.detectDeviceType();
|
|
507
442
|
}
|
|
508
443
|
const videoConstraints = {};
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
preferredCameraFacing: this.preferredCameraFacing,
|
|
512
|
-
preferredCamera: this.preferredCamera,
|
|
513
|
-
deviceType: this.deviceType,
|
|
514
|
-
isManuallySelected: this.isManuallySelected
|
|
515
|
-
});
|
|
516
|
-
if (this.isManuallySelected && this.selectedCameraId) {
|
|
517
|
-
// When manually selected, use deviceId for exact camera selection
|
|
444
|
+
// Priority 1: Always use deviceId if available (ensures specific camera with autofocus)
|
|
445
|
+
if (this.selectedCameraId) {
|
|
518
446
|
videoConstraints.deviceId = { exact: this.selectedCameraId };
|
|
519
|
-
this.logger.state('USANDO_CAMARA_MANUAL', {
|
|
520
|
-
deviceId: this.selectedCameraId
|
|
521
|
-
});
|
|
522
447
|
}
|
|
448
|
+
// Priority 2: Use facingMode as fallback when no specific camera is selected
|
|
523
449
|
else if (this.preferredCameraFacing) {
|
|
524
|
-
// Use facingMode for initial preference-based selection
|
|
525
|
-
// Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
|
|
526
450
|
const facingConstraint = this.deviceType === 'desktop'
|
|
527
451
|
? { ideal: this.preferredCameraFacing }
|
|
528
452
|
: { exact: this.preferredCameraFacing };
|
|
529
453
|
videoConstraints.facingMode = facingConstraint;
|
|
530
|
-
this.logger.state('USANDO_FACING_CONSTRAINT', {
|
|
531
|
-
facingMode: this.preferredCameraFacing,
|
|
532
|
-
constraintType: this.deviceType === 'desktop' ? 'ideal' : 'exact'
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
else if (this.selectedCameraId) {
|
|
536
|
-
videoConstraints.deviceId = { exact: this.selectedCameraId };
|
|
537
|
-
this.logger.state('USANDO_CAMARA_SELECCIONADA', {
|
|
538
|
-
deviceId: this.selectedCameraId
|
|
539
|
-
});
|
|
540
454
|
}
|
|
455
|
+
// Priority 3: Fallback to preferredCamera setting
|
|
541
456
|
else {
|
|
542
|
-
// Use preferredCamera setting to determine default facing mode
|
|
543
457
|
if (this.preferredCamera === 'front') {
|
|
544
458
|
videoConstraints.facingMode = "user";
|
|
545
459
|
}
|
|
@@ -547,24 +461,25 @@ class CameraService {
|
|
|
547
461
|
videoConstraints.facingMode = "environment";
|
|
548
462
|
}
|
|
549
463
|
else {
|
|
550
|
-
// Auto mode: use environment for mobile/tablet, user for desktop
|
|
551
464
|
videoConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
|
|
552
465
|
}
|
|
553
|
-
this.logger.state('USANDO_PREFERENCIA_CONFIGURADA', {
|
|
554
|
-
facingMode: videoConstraints.facingMode,
|
|
555
|
-
preferredCamera: this.preferredCamera,
|
|
556
|
-
deviceType: this.deviceType
|
|
557
|
-
});
|
|
558
466
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
467
|
+
// Optimization: Use cached capabilities if available
|
|
468
|
+
let capabilities;
|
|
469
|
+
if (this.selectedCameraId && CameraService.cameraCapabilitiesCache.has(this.selectedCameraId + '_caps')) {
|
|
470
|
+
capabilities = JSON.parse(CameraService.cameraCapabilitiesCache.get(this.selectedCameraId + '_caps'));
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
const tempStream = await navigator.mediaDevices.getUserMedia({ video: videoConstraints });
|
|
474
|
+
const videoTrack = tempStream.getVideoTracks()[0];
|
|
475
|
+
capabilities = videoTrack.getCapabilities();
|
|
476
|
+
tempStream.getTracks().forEach(track => track.stop());
|
|
477
|
+
// Cache capabilities for future use
|
|
478
|
+
if (this.selectedCameraId) {
|
|
479
|
+
CameraService.cameraCapabilitiesCache.set(this.selectedCameraId + '_caps', JSON.stringify(capabilities));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
565
482
|
const constraints = { ...videoConstraints };
|
|
566
|
-
// Enhanced focus and image quality settings
|
|
567
|
-
this.applyAdvancedCameraSettings(constraints, capabilities);
|
|
568
483
|
if (capabilities.width && capabilities.height) {
|
|
569
484
|
const maxWidth = Math.min(capabilities.width.max, 1920);
|
|
570
485
|
const maxHeight = Math.min(capabilities.height.max, 1080);
|
|
@@ -578,15 +493,20 @@ class CameraService {
|
|
|
578
493
|
constraints.height = { ideal: maxHeight };
|
|
579
494
|
}
|
|
580
495
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
496
|
+
// Configure autofocus if camera has the capability
|
|
497
|
+
const capabilitiesAny = capabilities;
|
|
498
|
+
if (capabilitiesAny.focusMode && Array.isArray(capabilitiesAny.focusMode)) {
|
|
499
|
+
if (capabilitiesAny.focusMode.includes('continuous')) {
|
|
500
|
+
constraints.focusMode = 'continuous';
|
|
501
|
+
}
|
|
502
|
+
else if (capabilitiesAny.focusMode.includes('auto')) {
|
|
503
|
+
constraints.focusMode = 'auto';
|
|
504
|
+
}
|
|
505
|
+
}
|
|
585
506
|
return constraints;
|
|
586
507
|
}
|
|
587
508
|
catch (err) {
|
|
588
|
-
|
|
589
|
-
// Ensure device type is detected before determining constraints
|
|
509
|
+
// Fallback logic with minimal temporary streams
|
|
590
510
|
if (!this.deviceType || this.deviceType === 'desktop') {
|
|
591
511
|
await this.detectDeviceType();
|
|
592
512
|
}
|
|
@@ -595,25 +515,16 @@ class CameraService {
|
|
|
595
515
|
width: { ideal: isTablet ? 1280 : 1920 },
|
|
596
516
|
height: { ideal: isTablet ? 720 : 1080 }
|
|
597
517
|
};
|
|
598
|
-
|
|
599
|
-
this.applyBasicFocusSettings(fallbackConstraints);
|
|
600
|
-
if (this.isManuallySelected && this.selectedCameraId) {
|
|
601
|
-
// When manually selected, use deviceId for exact camera selection
|
|
518
|
+
if (this.selectedCameraId) {
|
|
602
519
|
fallbackConstraints.deviceId = { exact: this.selectedCameraId };
|
|
603
520
|
}
|
|
604
521
|
else if (this.preferredCameraFacing) {
|
|
605
|
-
// Use facingMode for initial preference-based selection
|
|
606
|
-
// Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
|
|
607
522
|
const facingConstraint = this.deviceType === 'desktop'
|
|
608
523
|
? { ideal: this.preferredCameraFacing }
|
|
609
524
|
: { exact: this.preferredCameraFacing };
|
|
610
525
|
fallbackConstraints.facingMode = facingConstraint;
|
|
611
526
|
}
|
|
612
|
-
else if (this.selectedCameraId) {
|
|
613
|
-
fallbackConstraints.deviceId = { exact: this.selectedCameraId };
|
|
614
|
-
}
|
|
615
527
|
else {
|
|
616
|
-
// Use preferredCamera setting to determine default facing mode
|
|
617
528
|
if (this.preferredCamera === 'front') {
|
|
618
529
|
fallbackConstraints.facingMode = "user";
|
|
619
530
|
}
|
|
@@ -621,165 +532,26 @@ class CameraService {
|
|
|
621
532
|
fallbackConstraints.facingMode = "environment";
|
|
622
533
|
}
|
|
623
534
|
else {
|
|
624
|
-
// Auto mode: use environment for mobile/tablet, user for desktop
|
|
625
535
|
fallbackConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
|
|
626
536
|
}
|
|
627
537
|
}
|
|
628
538
|
return fallbackConstraints;
|
|
629
539
|
}
|
|
630
540
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if (capabilities.frameRate) {
|
|
635
|
-
constraints.frameRate = {
|
|
636
|
-
ideal: 30,
|
|
637
|
-
min: 15,
|
|
638
|
-
max: 60
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
// Width and height constraints for optimal resolution
|
|
642
|
-
if (capabilities.width && capabilities.height) {
|
|
643
|
-
constraints.width = {
|
|
644
|
-
ideal: Math.min(1920, capabilities.width.max || 1920),
|
|
645
|
-
min: 640
|
|
646
|
-
};
|
|
647
|
-
constraints.height = {
|
|
648
|
-
ideal: Math.min(1080, capabilities.height.max || 1080),
|
|
649
|
-
min: 480
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
// Apply enhanced constraints through advanced array for better browser compatibility
|
|
653
|
-
const advancedConstraints = [];
|
|
654
|
-
// Try to set optimal settings through advanced constraints
|
|
655
|
-
const advanced = {};
|
|
656
|
-
// Focus mode - use 'any' type to bypass TypeScript limitations
|
|
657
|
-
if (capabilities.focusMode) {
|
|
658
|
-
if (capabilities.focusMode.includes('continuous')) {
|
|
659
|
-
advanced.focusMode = 'continuous';
|
|
660
|
-
}
|
|
661
|
-
else if (capabilities.focusMode.includes('single-shot')) {
|
|
662
|
-
advanced.focusMode = 'single-shot';
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
// Focus distance for close objects
|
|
666
|
-
if (capabilities.focusDistance) {
|
|
667
|
-
advanced.focusDistance = {
|
|
668
|
-
ideal: 0.4, // 40cm - optimal for document capture
|
|
669
|
-
min: 0.2,
|
|
670
|
-
max: 1.0
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
// Zoom control
|
|
674
|
-
if (capabilities.zoom) {
|
|
675
|
-
advanced.zoom = {
|
|
676
|
-
ideal: 1.0,
|
|
677
|
-
min: capabilities.zoom.min,
|
|
678
|
-
max: Math.min(capabilities.zoom.max, 3.0)
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
// Add advanced constraints if any were set
|
|
682
|
-
if (Object.keys(advanced).length > 0) {
|
|
683
|
-
advancedConstraints.push(advanced);
|
|
684
|
-
}
|
|
685
|
-
if (advancedConstraints.length > 0) {
|
|
686
|
-
constraints.advanced = advancedConstraints;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
applyBasicFocusSettings(constraints) {
|
|
690
|
-
// Apply basic focus settings when capabilities are not available
|
|
691
|
-
// Use advanced constraints for non-standard properties
|
|
692
|
-
const advanced = {
|
|
693
|
-
focusMode: 'continuous',
|
|
694
|
-
exposureMode: 'continuous',
|
|
695
|
-
whiteBalanceMode: 'continuous'
|
|
696
|
-
};
|
|
697
|
-
if (!constraints.advanced) {
|
|
698
|
-
constraints.advanced = [];
|
|
699
|
-
}
|
|
700
|
-
constraints.advanced.push(advanced);
|
|
701
|
-
constraints.frameRate = { ideal: 30 };
|
|
702
|
-
}
|
|
703
|
-
getCapabilitiesSummary(capabilities) {
|
|
704
|
-
return {
|
|
705
|
-
hasAutoFocus: !!capabilities.focusMode,
|
|
706
|
-
hasFocusDistance: !!capabilities.focusDistance,
|
|
707
|
-
hasExposureControl: !!capabilities.exposureMode,
|
|
708
|
-
hasWhiteBalance: !!capabilities.whiteBalanceMode,
|
|
709
|
-
hasZoom: !!capabilities.zoom,
|
|
710
|
-
hasTorch: capabilities.torch !== undefined,
|
|
711
|
-
hasImageControls: !!(capabilities.brightness || capabilities.contrast || capabilities.saturation || capabilities.sharpness),
|
|
712
|
-
resolutionRange: capabilities.width && capabilities.height ?
|
|
713
|
-
`${capabilities.width.min}x${capabilities.height.min} - ${capabilities.width.max}x${capabilities.height.max}` : 'unknown'
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
// New method to enable torch/flash for better illumination
|
|
717
|
-
async setTorchEnabled(enabled, stream) {
|
|
718
|
-
try {
|
|
719
|
-
if (!stream) {
|
|
720
|
-
this.logger.warn('No hay stream disponible para controlar el flash');
|
|
721
|
-
return false;
|
|
722
|
-
}
|
|
723
|
-
const videoTrack = stream.getVideoTracks()[0];
|
|
724
|
-
if (!videoTrack) {
|
|
725
|
-
this.logger.warn('No hay track de video disponible');
|
|
726
|
-
return false;
|
|
727
|
-
}
|
|
728
|
-
const capabilities = videoTrack.getCapabilities();
|
|
729
|
-
if (capabilities.torch === undefined) {
|
|
730
|
-
this.logger.warn('El dispositivo no soporta control de flash');
|
|
731
|
-
return false;
|
|
732
|
-
}
|
|
733
|
-
await videoTrack.applyConstraints({
|
|
734
|
-
advanced: [{ torch: enabled }]
|
|
735
|
-
});
|
|
736
|
-
this.logger.state('FLASH_CONTROLADO', { enabled });
|
|
737
|
-
return true;
|
|
738
|
-
}
|
|
739
|
-
catch (error) {
|
|
740
|
-
this.logger.error('Error al controlar el flash:', error);
|
|
541
|
+
// Optimization helper methods
|
|
542
|
+
constraintsEqual(a, b) {
|
|
543
|
+
if (!a || !b)
|
|
741
544
|
return false;
|
|
742
|
-
|
|
545
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
743
546
|
}
|
|
744
|
-
//
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
if (!videoTrack) {
|
|
753
|
-
this.logger.warn('No hay track de video disponible');
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
const capabilities = videoTrack.getCapabilities();
|
|
757
|
-
if (!capabilities.focusMode || !capabilities.focusMode.includes('single-shot')) {
|
|
758
|
-
this.logger.warn('El dispositivo no soporta enfoque manual');
|
|
759
|
-
return false;
|
|
760
|
-
}
|
|
761
|
-
// Trigger single-shot focus
|
|
762
|
-
await videoTrack.applyConstraints({
|
|
763
|
-
advanced: [{ focusMode: 'single-shot' }]
|
|
764
|
-
});
|
|
765
|
-
// Return to continuous focus after a short delay
|
|
766
|
-
setTimeout(async () => {
|
|
767
|
-
try {
|
|
768
|
-
await videoTrack.applyConstraints({
|
|
769
|
-
advanced: [{ focusMode: 'continuous' }]
|
|
770
|
-
});
|
|
771
|
-
}
|
|
772
|
-
catch (error) {
|
|
773
|
-
this.logger.warn('Error al volver a enfoque continuo:', error);
|
|
774
|
-
}
|
|
775
|
-
}, 1000);
|
|
776
|
-
this.logger.state('ENFOQUE_MANUAL_ACTIVADO', { x, y });
|
|
777
|
-
return true;
|
|
778
|
-
}
|
|
779
|
-
catch (error) {
|
|
780
|
-
this.logger.error('Error al enfocar manualmente:', error);
|
|
781
|
-
return false;
|
|
782
|
-
}
|
|
547
|
+
// Cleanup method for optimization
|
|
548
|
+
static clearCaches() {
|
|
549
|
+
CameraService.cameraCapabilitiesCache.clear();
|
|
550
|
+
CameraService.deviceEnumerationCache = null;
|
|
551
|
+
}
|
|
552
|
+
// Method to invalidate device cache when needed
|
|
553
|
+
invalidateDeviceCache() {
|
|
554
|
+
CameraService.deviceEnumerationCache = null;
|
|
783
555
|
}
|
|
784
556
|
}
|
|
785
557
|
|
|
@@ -858,7 +630,6 @@ class DeviceStrategyFactory {
|
|
|
858
630
|
}
|
|
859
631
|
|
|
860
632
|
class DetectionService {
|
|
861
|
-
logger;
|
|
862
633
|
debug;
|
|
863
634
|
useDocumentClassification;
|
|
864
635
|
session;
|
|
@@ -875,8 +646,10 @@ class DetectionService {
|
|
|
875
646
|
preprocessCanvas;
|
|
876
647
|
preprocessCtx;
|
|
877
648
|
captureCanvas;
|
|
878
|
-
|
|
879
|
-
|
|
649
|
+
// Static canvas pool for reuse across instances
|
|
650
|
+
static canvasPool = new Map();
|
|
651
|
+
static MAX_POOL_SIZE = 5;
|
|
652
|
+
constructor(debug = false, useDocumentClassification = false) {
|
|
880
653
|
this.debug = debug;
|
|
881
654
|
this.useDocumentClassification = useDocumentClassification;
|
|
882
655
|
this.deviceStrategy = DeviceStrategyFactory.createStrategy();
|
|
@@ -884,21 +657,15 @@ class DetectionService {
|
|
|
884
657
|
}
|
|
885
658
|
async loadModel() {
|
|
886
659
|
if (this.modelLoaded || this.session) {
|
|
887
|
-
this.logger.state('MODELO_YA_PRECARGADO', {
|
|
888
|
-
sessionExists: !!this.session,
|
|
889
|
-
modelPreloaded: this.modelLoaded
|
|
890
|
-
});
|
|
891
660
|
return;
|
|
892
661
|
}
|
|
893
662
|
try {
|
|
894
|
-
this.logger.state('PRECARGANDO_MODELO_DETECCION', { modelPath: this.MODEL_PATH });
|
|
895
663
|
const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
|
|
896
664
|
try {
|
|
897
665
|
this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
|
|
898
666
|
}
|
|
899
667
|
catch (error) {
|
|
900
668
|
if (error.message.includes('failed to allocate a buffer')) {
|
|
901
|
-
this.logger.warn('Fallo en asignación de buffer durante precarga, intentando con configuración mínima');
|
|
902
669
|
const fallbackOptions = {
|
|
903
670
|
executionProviders: ['wasm'],
|
|
904
671
|
graphOptimizationLevel: 'disabled',
|
|
@@ -916,10 +683,8 @@ class DetectionService {
|
|
|
916
683
|
}
|
|
917
684
|
}
|
|
918
685
|
this.modelLoaded = true;
|
|
919
|
-
this.logger.state('MODELO_DETECCION_CARGADO_EXITOSAMENTE', { sessionCreated: !!this.session });
|
|
920
686
|
}
|
|
921
687
|
catch (error) {
|
|
922
|
-
this.logger.error('Error al precargar modelo de detección:', error);
|
|
923
688
|
throw error;
|
|
924
689
|
}
|
|
925
690
|
}
|
|
@@ -928,19 +693,14 @@ class DetectionService {
|
|
|
928
693
|
return;
|
|
929
694
|
}
|
|
930
695
|
try {
|
|
931
|
-
this.logger.state('CARGANDO_MODELO_MOBILENET', { path: this.MOBILENET_MODEL_PATH });
|
|
932
696
|
// Try to load class map (optional - SqueezeNet may not need it for basic classification)
|
|
933
697
|
try {
|
|
934
698
|
const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
|
|
935
699
|
if (classResponse.ok) {
|
|
936
700
|
this.mobileNetClassMap = await classResponse.json();
|
|
937
|
-
this.logger.state('CLASES_MOBILENET_CARGADAS', {
|
|
938
|
-
classCount: Object.keys(this.mobileNetClassMap).length
|
|
939
|
-
});
|
|
940
701
|
}
|
|
941
702
|
}
|
|
942
703
|
catch (error) {
|
|
943
|
-
this.logger.warn('No se pudo cargar el mapa de clases de MobileNet, usando clasificación básica');
|
|
944
704
|
// Create a basic class map for document types
|
|
945
705
|
this.mobileNetClassMap = {
|
|
946
706
|
"0": "document",
|
|
@@ -957,7 +717,6 @@ class DetectionService {
|
|
|
957
717
|
}
|
|
958
718
|
catch (error) {
|
|
959
719
|
if (error.message.includes('failed to allocate a buffer')) {
|
|
960
|
-
this.logger.warn('Fallo en asignación de buffer de MobileNet, intentando con configuración mínima');
|
|
961
720
|
const fallbackOptions = {
|
|
962
721
|
executionProviders: ['wasm'],
|
|
963
722
|
graphOptimizationLevel: 'disabled',
|
|
@@ -974,12 +733,8 @@ class DetectionService {
|
|
|
974
733
|
throw error;
|
|
975
734
|
}
|
|
976
735
|
}
|
|
977
|
-
this.logger.state('MODELO_MOBILENET_CARGADO_EXITOSAMENTE', {
|
|
978
|
-
sessionCreated: !!this.mobileNetSession
|
|
979
|
-
});
|
|
980
736
|
}
|
|
981
737
|
catch (error) {
|
|
982
|
-
this.logger.error('Error al cargar modelo MobileNet:', error);
|
|
983
738
|
throw error;
|
|
984
739
|
}
|
|
985
740
|
}
|
|
@@ -1032,11 +787,9 @@ class DetectionService {
|
|
|
1032
787
|
}
|
|
1033
788
|
async classifyDocument(canvas) {
|
|
1034
789
|
if (!this.mobileNetSession || !this.mobileNetClassMap) {
|
|
1035
|
-
this.logger.warn('Modelo MobileNet no está cargado, saltando clasificación');
|
|
1036
790
|
return null;
|
|
1037
791
|
}
|
|
1038
792
|
try {
|
|
1039
|
-
this.logger.state('CLASIFICANDO_DOCUMENTO', { timestamp: Date.now() });
|
|
1040
793
|
const inputTensor = this.preprocessMobileNet(canvas);
|
|
1041
794
|
const feeds = { [this.mobileNetSession.inputNames[0]]: inputTensor };
|
|
1042
795
|
const results = await this.mobileNetSession.run(feeds);
|
|
@@ -1044,13 +797,6 @@ class DetectionService {
|
|
|
1044
797
|
const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
|
|
1045
798
|
const confidence = output[maxIdx];
|
|
1046
799
|
const className = this.mobileNetClassMap[maxIdx.toString()] || "unknown";
|
|
1047
|
-
this.logger.state('DOCUMENTO_CLASIFICADO', {
|
|
1048
|
-
class: className,
|
|
1049
|
-
confidence: confidence.toFixed(3),
|
|
1050
|
-
classIndex: maxIdx,
|
|
1051
|
-
timestamp: Date.now(),
|
|
1052
|
-
results
|
|
1053
|
-
});
|
|
1054
800
|
return {
|
|
1055
801
|
class: className,
|
|
1056
802
|
confidence: confidence,
|
|
@@ -1058,7 +804,6 @@ class DetectionService {
|
|
|
1058
804
|
};
|
|
1059
805
|
}
|
|
1060
806
|
catch (error) {
|
|
1061
|
-
this.logger.error('Error al clasificar documento:', error);
|
|
1062
807
|
return null;
|
|
1063
808
|
}
|
|
1064
809
|
}
|
|
@@ -1171,7 +916,6 @@ class DetectionService {
|
|
|
1171
916
|
}
|
|
1172
917
|
this.mobileNetClassMap = undefined;
|
|
1173
918
|
this.modelLoaded = false;
|
|
1174
|
-
this.logger.state('DETECCION_SERVICE_LIMPIADO', { timestamp: Date.now() });
|
|
1175
919
|
}
|
|
1176
920
|
initializeCanvasPool() {
|
|
1177
921
|
this.preprocessCanvas = document.createElement('canvas');
|
|
@@ -1182,9 +926,6 @@ class DetectionService {
|
|
|
1182
926
|
willReadFrequently: true
|
|
1183
927
|
});
|
|
1184
928
|
this.captureCanvas = document.createElement('canvas');
|
|
1185
|
-
this.logger.state('CANVAS_POOL_INICIALIZADO', {
|
|
1186
|
-
preprocessCanvasSize: this.INPUT_SIZE
|
|
1187
|
-
});
|
|
1188
929
|
}
|
|
1189
930
|
cleanupCanvasPool() {
|
|
1190
931
|
if (this.preprocessCanvas) {
|
|
@@ -1220,10 +961,11 @@ class DetectionService {
|
|
|
1220
961
|
return (sign << 15) | (newExp << 10) | (frac >> 13);
|
|
1221
962
|
}
|
|
1222
963
|
preprocessMobileNet(canvas) {
|
|
1223
|
-
|
|
1224
|
-
tempCanvas
|
|
1225
|
-
tempCanvas.height = 224;
|
|
964
|
+
// Use pooled canvas instead of creating new one
|
|
965
|
+
const tempCanvas = this.getCanvas(224, 224);
|
|
1226
966
|
const tempCtx = tempCanvas.getContext('2d');
|
|
967
|
+
// Clear canvas before use
|
|
968
|
+
tempCtx.clearRect(0, 0, 224, 224);
|
|
1227
969
|
// Resize image to 224x224 for SqueezeNet (using MobileNet interface)
|
|
1228
970
|
tempCtx.drawImage(canvas, 0, 0, 224, 224);
|
|
1229
971
|
const imageData = tempCtx.getImageData(0, 0, 224, 224);
|
|
@@ -1239,8 +981,53 @@ class DetectionService {
|
|
|
1239
981
|
// Blue channel
|
|
1240
982
|
arr[2 * hw + i] = data[i * 4 + 2] / 255.0;
|
|
1241
983
|
}
|
|
984
|
+
// Return canvas to pool after use
|
|
985
|
+
this.returnCanvas(tempCanvas);
|
|
1242
986
|
return new window.ort.Tensor('float32', arr, [1, 3, 224, 224]);
|
|
1243
987
|
}
|
|
988
|
+
// Canvas pool management methods for optimization
|
|
989
|
+
getCanvas(width, height) {
|
|
990
|
+
const key = `${width}x${height}`;
|
|
991
|
+
if (!DetectionService.canvasPool.has(key)) {
|
|
992
|
+
DetectionService.canvasPool.set(key, []);
|
|
993
|
+
}
|
|
994
|
+
const pool = DetectionService.canvasPool.get(key);
|
|
995
|
+
let canvas = pool.pop();
|
|
996
|
+
if (!canvas) {
|
|
997
|
+
canvas = document.createElement('canvas');
|
|
998
|
+
canvas.width = width;
|
|
999
|
+
canvas.height = height;
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
// Clear the canvas before reuse
|
|
1003
|
+
const ctx = canvas.getContext('2d');
|
|
1004
|
+
ctx.clearRect(0, 0, width, height);
|
|
1005
|
+
}
|
|
1006
|
+
return canvas;
|
|
1007
|
+
}
|
|
1008
|
+
returnCanvas(canvas) {
|
|
1009
|
+
const key = `${canvas.width}x${canvas.height}`;
|
|
1010
|
+
if (!DetectionService.canvasPool.has(key)) {
|
|
1011
|
+
DetectionService.canvasPool.set(key, []);
|
|
1012
|
+
}
|
|
1013
|
+
const pool = DetectionService.canvasPool.get(key);
|
|
1014
|
+
// Only return to pool if not at max capacity
|
|
1015
|
+
if (pool.length < DetectionService.MAX_POOL_SIZE) {
|
|
1016
|
+
pool.push(canvas);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
// Static method to clear all canvas pools (useful for memory management)
|
|
1020
|
+
static clearCanvasPools() {
|
|
1021
|
+
DetectionService.canvasPool.clear();
|
|
1022
|
+
}
|
|
1023
|
+
// Method to get pool statistics for debugging
|
|
1024
|
+
getPoolStats() {
|
|
1025
|
+
const stats = {};
|
|
1026
|
+
DetectionService.canvasPool.forEach((pool, key) => {
|
|
1027
|
+
stats[key] = pool.length;
|
|
1028
|
+
});
|
|
1029
|
+
return stats;
|
|
1030
|
+
}
|
|
1244
1031
|
}
|
|
1245
1032
|
|
|
1246
1033
|
class ServiceContainer {
|
|
@@ -1251,13 +1038,11 @@ class ServiceContainer {
|
|
|
1251
1038
|
initializeServices(config) {
|
|
1252
1039
|
// Initialize services in dependency order
|
|
1253
1040
|
const eventBus = new EventBusService();
|
|
1254
|
-
const logger = new LoggerService(config.debug);
|
|
1255
1041
|
const stateManager = new StateManagerService(eventBus);
|
|
1256
|
-
const cameraService = new CameraService(
|
|
1257
|
-
const detectionService = new DetectionService(
|
|
1042
|
+
const cameraService = new CameraService(eventBus, config.preferredCamera);
|
|
1043
|
+
const detectionService = new DetectionService(config.debug, config.useDocumentClassification);
|
|
1258
1044
|
// Register services
|
|
1259
1045
|
this.services.set('eventBus', eventBus);
|
|
1260
|
-
this.services.set('logger', logger);
|
|
1261
1046
|
this.services.set('stateManager', stateManager);
|
|
1262
1047
|
this.services.set('cameraService', cameraService);
|
|
1263
1048
|
this.services.set('detectionService', detectionService);
|
|
@@ -1269,9 +1054,6 @@ class ServiceContainer {
|
|
|
1269
1054
|
}
|
|
1270
1055
|
return service;
|
|
1271
1056
|
}
|
|
1272
|
-
getLogger() {
|
|
1273
|
-
return this.get('logger');
|
|
1274
|
-
}
|
|
1275
1057
|
getEventBus() {
|
|
1276
1058
|
return this.get('eventBus');
|
|
1277
1059
|
}
|
|
@@ -1285,10 +1067,6 @@ class ServiceContainer {
|
|
|
1285
1067
|
return this.get('detectionService');
|
|
1286
1068
|
}
|
|
1287
1069
|
updateConfig(config) {
|
|
1288
|
-
if (config.debug !== undefined) {
|
|
1289
|
-
const logger = this.services.get('logger');
|
|
1290
|
-
logger.setDebugMode(config.debug);
|
|
1291
|
-
}
|
|
1292
1070
|
}
|
|
1293
1071
|
cleanup() {
|
|
1294
1072
|
// Cleanup services in reverse order
|
|
@@ -1298,7 +1076,7 @@ class ServiceContainer {
|
|
|
1298
1076
|
}
|
|
1299
1077
|
}
|
|
1300
1078
|
|
|
1301
|
-
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}}";
|
|
1079
|
+
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}}";
|
|
1302
1080
|
|
|
1303
1081
|
const JaakStamps = class {
|
|
1304
1082
|
constructor(hostRef) {
|
|
@@ -1328,9 +1106,16 @@ const JaakStamps = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
1908
1817
|
step: 'front',
|
|
1909
1818
|
isCapturing: false
|
|
1910
1819
|
};
|
|
1911
|
-
const cameraInfo = this.
|
|
1912
|
-
|
|
1913
|
-
selectedCameraId: null,
|
|
1914
|
-
deviceType: 'desktop'};
|
|
1915
|
-
return (index.h("div", { key: '9d4d77042218ab3d0bc4fb1ce879db09dc3e78b4', class: "detector-container" }, index.h("div", { key: '51ac8ccb1275637282b038ad8364d19f14ef61b1', class: "video-container" }, index.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' } }), index.h("div", { key: '43b71dfe3eaf20ffa66a4139e24b1af6a2074631', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
|
|
1820
|
+
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
1821
|
+
return (index.h("div", { key: '69a8df7969288e19088e8809f1fefcc89b237cda', class: "detector-container" }, index.h("div", { key: '79e59a315d266be9d4fb5eed5740e5ad825d5890', class: "video-container" }, index.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' } }), index.h("div", { key: 'ddab107500c2dab24c140049bef0b44aafc35dc7', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, 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 = class {
|
|
|
1921
1827
|
border: '2px solid #32406C',
|
|
1922
1828
|
pointerEvents: 'none',
|
|
1923
1829
|
boxSizing: 'border-box'
|
|
1924
|
-
} })))), this.isMaskReady && (index.h("div", { key: '
|
|
1830
|
+
} })))), this.isMaskReady && (index.h("div", { key: '370337089a3de949b8cb4ae84dc0037e6044b2d5', class: "overlay-mask" }, index.h("div", { key: 'cb35c2e42bf1729ad02a8fe7e7abaf27cf1a8d7a', class: "card-outline" }, index.h("div", { key: '637d72ada63805e7f25051cd1ad9093030cb025a', class: "side side-top" }), index.h("div", { key: 'c7a465aa686afa41f80433d949715b40f6f349dc', class: "side side-right" }), index.h("div", { key: '384aade0610b5bc7bdd7db350a70d82f9bebd8ab', class: "side side-bottom" }), index.h("div", { key: '5506cc346f3fd718e3fda4365f69ea780304b6d6', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'f5b9b62937eabab7c096b7417a13bbf6f822ee8a', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '74b122125a42839d2600cf99fb855140042ab761', class: "skip-section" }, index.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"), index.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 && (index.h("div", { key: '
|
|
1832
|
+
: 'Saltar reverso'))), captureState.isVideoActive && (index.h("div", { key: 'f04e47749a25197c434b849844c67eadca5c6af3', class: "camera-controls" }, index.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 ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: '61cfb89cd9bb549cfff2cb70293db06ee6cdf06b', class: "camera-selector-dropdown" }, index.h("div", { key: '60f751d8db7b305f2bdfdc089d2110bcff940150', class: "camera-selector-header" }, index.h("span", { key: '9dae045a34cc06f323ff4850921b495fc845be47' }, "Seleccionar C\u00E1mara"), index.h("button", { key: '4584d7964d91ddd7f3d980c3e5e90493530d33bd', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '9881f2b4fc6ffbdc76db06a7b49d060cefbe6277', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '3dfe9ffc7b4cc9afb199a78026cef6d4b469805e', class: "device-info" }, index.h("small", { key: 'bddc865a30469bd2724624b2f7c8f641f9d97a07' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (index.h("div", { key: '2fd963e44f69f80d17c6bbbfa3c9b5e11bc816be', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: 'ba4872ea5388a0ac3134be61feff5412373465ab', class: "flip-animation" }, index.h("div", { key: 'e83a92cea79f3e85aefc6964c5d9de7b495d511e', class: "id-card-icon" }), index.h("div", { key: '1d06b51fd6ad14d9b37aa6bca72bd13c951a386a', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '503c377896d5fdc3ae8476b09f511d571f23ae72', class: "success-animation" }, index.h("div", { key: 'e1929c245b0d1479a6fb1513b0b37c53070c4291', class: "check-icon" }), index.h("div", { key: '024c897c70427b205390770fc3dfb999ffcc84d3', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '54e350707628e5c88ae2900e412e6bda2eace943', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '82ea10569ed46285686cf3e63885ca1941bb559c', class: "status-spinner" })), index.h("div", { key: '1fecf179c10965cccf1be3dc0a54afd377ba38ad', class: "status-content" }, index.h("div", { key: '1b301c1bbb02e5cac2e6babcb20c190662d45c68', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: '5b6c61308d17176daade02c951a436d114a74100', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: 'b8104db64208dc87a617a072957c007f28055194', class: "performance-monitor" }, index.h("div", { key: 'e66aec071f10821be2713858dcc251c04f480e63', class: "performance-expanded" }, index.h("div", { key: '1ae09a91d0704ab21b9eb51097cf452fd5f44359', class: "metrics-row" }, index.h("div", { key: '340d4d16d048a5217fe13d5de63618c9ea221165', class: "metric-compact" }, index.h("span", { key: '20cfda14969ef70d232edd68f2e9902bd44b5d9a', class: "metric-label" }, "FPS"), index.h("span", { key: '3a089c2814a71ed6513bf05b0120a66aa159daee', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '23f700def988c3220fc5df37aeb57a0a20ff56b3', class: "metric-compact" }, index.h("span", { key: '441e8490a871469813a6d832114e03583d375e91', class: "metric-label" }, "MEM"), index.h("span", { key: '51f7c49164c9d5e699141c92c88375fc99b312ed', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: '6547fee80e2a1db7c9ff889a1c478fc8248159e6', class: "metrics-row" }, index.h("div", { key: '7b2d1642fd122ee0fbed36387afb23152afe10c2', class: "metric-compact" }, index.h("span", { key: 'a9137119bf7da366f18feaf8f28807ee40ad7a25', class: "metric-label" }, "INF"), index.h("span", { key: '5cbc0c040102688f1039cdcd4bb58020248cf700', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: 'cb4a86f5da46e4081d997c8b8bb6a899f06dfcd2', class: "metric-compact" }, index.h("span", { key: '88ad5e1d842eb156cf6813ecaa9d2bec76a5999e', class: "metric-label" }, "FRAME"), index.h("span", { key: 'a9b14cd98b64dcfc058d3b68a3c168a9e092fb3c', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '86e01f3596682cb8ad2ff5464748e6ecb7db374a', class: "metrics-row" }, index.h("div", { key: 'e73b9d39e9bcbc5d46fa0ba0a737700e3e5566d9', class: "metric-compact" }, index.h("span", { key: '8ecfe969dfe77c87d9da76d7390706014b4872f3', class: "metric-label" }, "DET"), index.h("span", { key: 'bd1a6a52e0bd1fb4d377413d7515e4e0f00da10f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '36539ae98787fd60642082db1bf89031003c4bef', class: "metric-compact" }, index.h("span", { key: '7f3a81629c09053e3beeca97f485da0f60412052', class: "metric-label" }, "RATE"), index.h("span", { key: '72399e0ac39270c82b265ce64e402f84cda9fb2c', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '4e3a914ba2f5993f85de7b9dd6dc93d76b41c96d', class: "watermark" }, index.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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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');
|
|
@@ -2155,11 +2036,6 @@ const JaakStamps = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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 = class {
|
|
|
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
|
};
|
|
2332
2267
|
JaakStamps.style = myComponentCss;
|