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