@jaak.ai/stamps 2.0.0-dev.25 → 2.0.0-dev.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/cjs/{index-DGM9-FNg.js → index-BfhtOB0D.js} +5 -2
- package/dist/cjs/index-BfhtOB0D.js.map +1 -0
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +2 -2
- package/dist/cjs/jaak-stamps.cjs.entry.js +431 -23
- 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 +2 -2
- package/dist/collection/components/my-component/my-component.css +252 -0
- package/dist/collection/components/my-component/my-component.js +493 -22
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/jaak-stamps.js +438 -23
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/{index-DqoVMnc7.js → index-BP1Q4KOg.js} +5 -2
- package/dist/esm/index-BP1Q4KOg.js.map +1 -0
- package/dist/esm/jaak-stamps-webcomponent.js +3 -3
- package/dist/esm/jaak-stamps.entry.js +431 -23
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +3 -3
- 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-BP1Q4KOg.js +3 -0
- package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js.map +1 -0
- package/dist/jaak-stamps-webcomponent/p-eb318f43.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-eb318f43.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +36 -0
- package/dist/types/components.d.ts +10 -0
- package/package.json +1 -1
- package/dist/cjs/index-DGM9-FNg.js.map +0 -1
- package/dist/esm/index-DqoVMnc7.js.map +0 -1
- package/dist/jaak-stamps-webcomponent/p-DqoVMnc7.js +0 -3
- package/dist/jaak-stamps-webcomponent/p-DqoVMnc7.js.map +0 -1
- package/dist/jaak-stamps-webcomponent/p-f1172b06.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-f1172b06.entry.js.map +0 -1
|
@@ -6,6 +6,7 @@ export class JaakStamps {
|
|
|
6
6
|
maskSize = 90; // Porcentaje del video que ocupará la máscara (default 90%)
|
|
7
7
|
cropMargin = 0; // Margen en píxeles para el recorte del documento (default 0)
|
|
8
8
|
useDocumentClassification = false; // Habilita la clasificación automática de documentos para determinar si solicitar reverso
|
|
9
|
+
preferredCamera = 'auto'; // Define qué cámara usar: 'auto' (automático según dispositivo), 'front' (frontal), 'back' (trasera)
|
|
9
10
|
captureCompleted;
|
|
10
11
|
isReady;
|
|
11
12
|
isVideoActive = false;
|
|
@@ -31,6 +32,10 @@ export class JaakStamps {
|
|
|
31
32
|
isLoading = false;
|
|
32
33
|
isModelPreloaded = false;
|
|
33
34
|
isMaskReady = false;
|
|
35
|
+
availableCameras = [];
|
|
36
|
+
selectedCameraId = null;
|
|
37
|
+
showCameraSelector = false;
|
|
38
|
+
isMultipleCamerasAvailable = false;
|
|
34
39
|
videoRef;
|
|
35
40
|
canvasRef;
|
|
36
41
|
session;
|
|
@@ -41,6 +46,9 @@ export class JaakStamps {
|
|
|
41
46
|
lastDetectedBox;
|
|
42
47
|
mobileNetSession;
|
|
43
48
|
mobileNetClassMap;
|
|
49
|
+
// Camera management properties
|
|
50
|
+
deviceType = 'desktop';
|
|
51
|
+
preferredCameraFacing = null;
|
|
44
52
|
// Performance optimization properties
|
|
45
53
|
frameSkipCounter = 0;
|
|
46
54
|
FRAME_SKIP = 2; // Process every 3rd frame (reduces CPU by ~66%)
|
|
@@ -77,6 +85,13 @@ export class JaakStamps {
|
|
|
77
85
|
this.cropMargin = 0;
|
|
78
86
|
}
|
|
79
87
|
}
|
|
88
|
+
validatePreferredCamera() {
|
|
89
|
+
const validOptions = ['auto', 'front', 'back'];
|
|
90
|
+
if (!validOptions.includes(this.preferredCamera)) {
|
|
91
|
+
console.warn(`preferredCamera debe ser uno de: ${validOptions.join(', ')}. Valor actual: ${this.preferredCamera}. Usando valor por defecto: 'auto'`);
|
|
92
|
+
this.preferredCamera = 'auto';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
80
95
|
emitReadyEvent() {
|
|
81
96
|
const isDocumentReady = !!window.ort && this.isModelPreloaded;
|
|
82
97
|
this.isReady.emit(isDocumentReady);
|
|
@@ -89,21 +104,318 @@ export class JaakStamps {
|
|
|
89
104
|
const settings = videoTrack.getSettings();
|
|
90
105
|
return settings.facingMode === 'environment';
|
|
91
106
|
}
|
|
107
|
+
async detectDeviceTypeAndCameras() {
|
|
108
|
+
// Detect device type
|
|
109
|
+
const userAgent = navigator.userAgent;
|
|
110
|
+
const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
|
111
|
+
const isTablet = /iPad|Android/i.test(userAgent) && window.innerWidth >= 768;
|
|
112
|
+
if (isTablet) {
|
|
113
|
+
this.deviceType = 'tablet';
|
|
114
|
+
}
|
|
115
|
+
else if (isMobile) {
|
|
116
|
+
this.deviceType = 'mobile';
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
this.deviceType = 'desktop';
|
|
120
|
+
}
|
|
121
|
+
this.debugLog('📱 Device type detected:', this.deviceType);
|
|
122
|
+
// Enumerate available cameras
|
|
123
|
+
await this.enumerateAndDetectCameras();
|
|
124
|
+
// Load user preference
|
|
125
|
+
this.loadCameraPreference();
|
|
126
|
+
}
|
|
127
|
+
async enumerateAndDetectCameras() {
|
|
128
|
+
try {
|
|
129
|
+
// First, check if we have permission to enumerate devices
|
|
130
|
+
const permissionStatus = await this.checkCameraPermission();
|
|
131
|
+
if (permissionStatus === 'denied') {
|
|
132
|
+
this.debugLog('❌ Camera permission denied');
|
|
133
|
+
this.statusMessage = "Permiso de cámara denegado";
|
|
134
|
+
this.statusColor = "#ff6b6b";
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Request minimal permission to get device labels
|
|
138
|
+
if (permissionStatus === 'prompt') {
|
|
139
|
+
const tempStream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
140
|
+
tempStream.getTracks().forEach(track => track.stop());
|
|
141
|
+
}
|
|
142
|
+
// Now enumerate devices with labels
|
|
143
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
144
|
+
this.availableCameras = devices.filter(device => device.kind === 'videoinput');
|
|
145
|
+
this.isMultipleCamerasAvailable = this.availableCameras.length > 1;
|
|
146
|
+
this.debugLog('📹 Available cameras:', {
|
|
147
|
+
count: this.availableCameras.length,
|
|
148
|
+
isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
|
|
149
|
+
cameras: this.availableCameras.map(cam => ({
|
|
150
|
+
id: cam.deviceId,
|
|
151
|
+
label: cam.label || 'Unknown Camera'
|
|
152
|
+
}))
|
|
153
|
+
});
|
|
154
|
+
// Set initial camera preference based on device type
|
|
155
|
+
this.setInitialCameraPreference();
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
this.debugLog('❌ Error enumerating cameras:', error);
|
|
159
|
+
this.handleCameraPermissionError(error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async checkCameraPermission() {
|
|
163
|
+
try {
|
|
164
|
+
if (!navigator.permissions) {
|
|
165
|
+
return 'prompt'; // Assume we need to prompt on older browsers
|
|
166
|
+
}
|
|
167
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
168
|
+
return permission.state;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
this.debugLog('⚠️ Could not check camera permission:', error);
|
|
172
|
+
return 'prompt';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
handleCameraPermissionError(error) {
|
|
176
|
+
if (error.name === 'NotAllowedError') {
|
|
177
|
+
this.statusMessage = "Permiso de cámara denegado. Active el permiso en configuración.";
|
|
178
|
+
this.statusColor = "#ff6b6b";
|
|
179
|
+
this.availableCameras = [];
|
|
180
|
+
this.isMultipleCamerasAvailable = false;
|
|
181
|
+
}
|
|
182
|
+
else if (error.name === 'NotFoundError') {
|
|
183
|
+
this.statusMessage = "No se encontraron cámaras disponibles.";
|
|
184
|
+
this.statusColor = "#ff6b6b";
|
|
185
|
+
this.availableCameras = [];
|
|
186
|
+
this.isMultipleCamerasAvailable = false;
|
|
187
|
+
}
|
|
188
|
+
else if (error.name === 'NotReadableError') {
|
|
189
|
+
this.statusMessage = "Cámara en uso por otra aplicación.";
|
|
190
|
+
this.statusColor = "#ff6b6b";
|
|
191
|
+
this.availableCameras = [];
|
|
192
|
+
this.isMultipleCamerasAvailable = false;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.statusMessage = "Error al acceder a las cámaras.";
|
|
196
|
+
this.statusColor = "#ff6b6b";
|
|
197
|
+
this.availableCameras = [];
|
|
198
|
+
this.isMultipleCamerasAvailable = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
setInitialCameraPreference() {
|
|
202
|
+
if (this.availableCameras.length === 0)
|
|
203
|
+
return;
|
|
204
|
+
// Apply user preference for camera selection
|
|
205
|
+
if (this.preferredCamera === 'front') {
|
|
206
|
+
// User explicitly wants front camera
|
|
207
|
+
this.preferredCameraFacing = 'user';
|
|
208
|
+
const frontCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('front') ||
|
|
209
|
+
camera.label.toLowerCase().includes('user') ||
|
|
210
|
+
camera.label.toLowerCase().includes('selfie') ||
|
|
211
|
+
!camera.label.toLowerCase().includes('back') && !camera.label.toLowerCase().includes('rear'));
|
|
212
|
+
if (frontCamera) {
|
|
213
|
+
this.selectedCameraId = frontCamera.deviceId;
|
|
214
|
+
this.debugLog('👤 User selected front camera:', frontCamera.label);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
this.selectedCameraId = this.availableCameras[0].deviceId;
|
|
218
|
+
this.debugLog('⚠️ Front camera not found, using first available:', this.availableCameras[0].label);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else if (this.preferredCamera === 'back') {
|
|
222
|
+
// User explicitly wants back camera
|
|
223
|
+
this.preferredCameraFacing = 'environment';
|
|
224
|
+
const backCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('back') ||
|
|
225
|
+
camera.label.toLowerCase().includes('rear') ||
|
|
226
|
+
camera.label.toLowerCase().includes('environment'));
|
|
227
|
+
if (backCamera) {
|
|
228
|
+
this.selectedCameraId = backCamera.deviceId;
|
|
229
|
+
this.debugLog('📷 User selected back camera:', backCamera.label);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
this.selectedCameraId = this.availableCameras[0].deviceId;
|
|
233
|
+
this.debugLog('⚠️ Back camera not found, using first available:', this.availableCameras[0].label);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Auto mode - use device type to determine best camera
|
|
238
|
+
if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
|
|
239
|
+
// For mobile/tablet, prefer rear camera for document scanning
|
|
240
|
+
this.preferredCameraFacing = 'environment';
|
|
241
|
+
const rearCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('back') ||
|
|
242
|
+
camera.label.toLowerCase().includes('rear') ||
|
|
243
|
+
camera.label.toLowerCase().includes('environment'));
|
|
244
|
+
if (rearCamera) {
|
|
245
|
+
this.selectedCameraId = rearCamera.deviceId;
|
|
246
|
+
this.debugLog('📱 Auto-selected rear camera for mobile:', rearCamera.label);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
this.selectedCameraId = this.availableCameras[0].deviceId;
|
|
250
|
+
this.debugLog('📱 Rear camera not found, using first available:', this.availableCameras[0].label);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// For desktop, use first available camera (usually the only one)
|
|
255
|
+
this.selectedCameraId = this.availableCameras[0].deviceId;
|
|
256
|
+
this.debugLog('💻 Auto-selected desktop camera:', this.availableCameras[0].label);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
loadCameraPreference() {
|
|
261
|
+
try {
|
|
262
|
+
const saved = localStorage.getItem('jaak-stamps-camera-preference');
|
|
263
|
+
if (saved) {
|
|
264
|
+
const preference = JSON.parse(saved);
|
|
265
|
+
// Validate that the saved camera is still available
|
|
266
|
+
const isStillAvailable = this.availableCameras.some(camera => camera.deviceId === preference.cameraId);
|
|
267
|
+
if (isStillAvailable) {
|
|
268
|
+
this.selectedCameraId = preference.cameraId;
|
|
269
|
+
this.preferredCameraFacing = preference.facing;
|
|
270
|
+
this.debugLog('💾 Loaded camera preference:', preference);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
this.debugLog('⚠️ Error loading camera preference:', error);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
saveCameraPreference() {
|
|
279
|
+
try {
|
|
280
|
+
const preference = {
|
|
281
|
+
cameraId: this.selectedCameraId,
|
|
282
|
+
facing: this.preferredCameraFacing,
|
|
283
|
+
timestamp: Date.now()
|
|
284
|
+
};
|
|
285
|
+
localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
|
|
286
|
+
this.debugLog('💾 Saved camera preference:', preference);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
this.debugLog('⚠️ Error saving camera preference:', error);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async switchCamera(cameraId) {
|
|
293
|
+
if (!this.isVideoActive || this.selectedCameraId === cameraId)
|
|
294
|
+
return;
|
|
295
|
+
try {
|
|
296
|
+
// Check if the selected camera is still available
|
|
297
|
+
const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
|
|
298
|
+
if (!selectedCamera) {
|
|
299
|
+
this.debugLog('❌ Selected camera not found, re-enumerating...');
|
|
300
|
+
await this.enumerateAndDetectCameras();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// Show loading state
|
|
304
|
+
if (this.debug) {
|
|
305
|
+
this.statusMessage = "Cambiando cámara...";
|
|
306
|
+
this.statusColor = "#007bff";
|
|
307
|
+
}
|
|
308
|
+
// Stop current stream
|
|
309
|
+
if (this.videoStream) {
|
|
310
|
+
this.videoStream.getTracks().forEach(track => track.stop());
|
|
311
|
+
}
|
|
312
|
+
// Update selected camera
|
|
313
|
+
this.selectedCameraId = cameraId;
|
|
314
|
+
// Update facing mode preference
|
|
315
|
+
const isRearCamera = selectedCamera.label.toLowerCase().includes('back') ||
|
|
316
|
+
selectedCamera.label.toLowerCase().includes('rear') ||
|
|
317
|
+
selectedCamera.label.toLowerCase().includes('environment');
|
|
318
|
+
this.preferredCameraFacing = isRearCamera ? 'environment' : 'user';
|
|
319
|
+
// Save preference
|
|
320
|
+
this.saveCameraPreference();
|
|
321
|
+
// Setup new camera with error handling
|
|
322
|
+
await this.setupCameraWithRetry();
|
|
323
|
+
this.debugLog('🔄 Switched to camera:', selectedCamera.label);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
this.debugLog('❌ Error switching camera:', error);
|
|
327
|
+
this.handleCameraPermissionError(error);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async setupCameraWithRetry(maxRetries = 3) {
|
|
331
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
332
|
+
try {
|
|
333
|
+
await this.setupCamera();
|
|
334
|
+
if (this.debug) {
|
|
335
|
+
this.statusMessage = "Cámara configurada correctamente";
|
|
336
|
+
this.statusColor = "#28a745";
|
|
337
|
+
}
|
|
338
|
+
return; // Success
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
this.debugLog(`❌ Camera setup attempt ${attempt} failed:`, error);
|
|
342
|
+
if (attempt === maxRetries) {
|
|
343
|
+
// Last attempt failed, handle the error
|
|
344
|
+
this.statusMessage = "Error al configurar la cámara";
|
|
345
|
+
this.statusColor = "#ff6b6b";
|
|
346
|
+
this.handleCameraPermissionError(error);
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
// Update status for retry attempt
|
|
350
|
+
if (this.debug) {
|
|
351
|
+
this.statusMessage = `Reintentando configuración de cámara... (${attempt}/${maxRetries})`;
|
|
352
|
+
this.statusColor = "#ffb366";
|
|
353
|
+
}
|
|
354
|
+
// Wait before retrying
|
|
355
|
+
await new Promise(resolve => setTimeout(resolve, 500 * attempt));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
toggleCameraSelector() {
|
|
360
|
+
this.showCameraSelector = !this.showCameraSelector;
|
|
361
|
+
this.debugLog('📹 Camera selector toggled:', {
|
|
362
|
+
showCameraSelector: this.showCameraSelector,
|
|
363
|
+
isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
|
|
364
|
+
availableCameras: this.availableCameras.length,
|
|
365
|
+
isVideoActive: this.isVideoActive
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
async flipCamera() {
|
|
369
|
+
if (!this.isMultipleCamerasAvailable)
|
|
370
|
+
return;
|
|
371
|
+
const currentIndex = this.availableCameras.findIndex(camera => camera.deviceId === this.selectedCameraId);
|
|
372
|
+
const nextIndex = (currentIndex + 1) % this.availableCameras.length;
|
|
373
|
+
const nextCamera = this.availableCameras[nextIndex];
|
|
374
|
+
await this.switchCamera(nextCamera.deviceId);
|
|
375
|
+
}
|
|
92
376
|
async componentDidLoad() {
|
|
377
|
+
if (this.debug) {
|
|
378
|
+
// Show detailed initialization loading state only in debug mode
|
|
379
|
+
this.isLoading = true;
|
|
380
|
+
this.statusMessage = 'Inicializando componente...';
|
|
381
|
+
this.statusColor = '#007bff';
|
|
382
|
+
// Small delay to ensure initialization message is visible
|
|
383
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
384
|
+
}
|
|
93
385
|
this.validateMaskSize();
|
|
94
386
|
this.validateCropMargin();
|
|
387
|
+
this.validatePreferredCamera();
|
|
388
|
+
if (this.debug) {
|
|
389
|
+
// Update status for camera detection
|
|
390
|
+
this.statusMessage = 'Detectando cámaras disponibles...';
|
|
391
|
+
}
|
|
392
|
+
await this.detectDeviceTypeAndCameras();
|
|
95
393
|
if (!window.ort) {
|
|
394
|
+
if (this.debug) {
|
|
395
|
+
// Update status for ONNX runtime loading
|
|
396
|
+
this.statusMessage = 'Cargando librerías de IA...';
|
|
397
|
+
}
|
|
96
398
|
const script = document.createElement('script');
|
|
97
399
|
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
|
|
98
400
|
document.head.appendChild(script);
|
|
99
401
|
// Wait for ONNX runtime to load before emitting ready event
|
|
100
402
|
script.onload = () => {
|
|
101
|
-
|
|
403
|
+
setTimeout(() => {
|
|
404
|
+
this.statusMessage = 'Presione el botón para activar la cámara';
|
|
405
|
+
this.statusColor = '#aaa';
|
|
406
|
+
this.isLoading = false;
|
|
407
|
+
this.emitReadyEvent();
|
|
408
|
+
}, 300);
|
|
102
409
|
};
|
|
103
410
|
}
|
|
104
411
|
else {
|
|
105
412
|
// ONNX runtime already loaded
|
|
106
|
-
|
|
413
|
+
setTimeout(() => {
|
|
414
|
+
this.statusMessage = 'Presione el botón para activar la cámara';
|
|
415
|
+
this.statusColor = '#aaa';
|
|
416
|
+
this.isLoading = false;
|
|
417
|
+
this.emitReadyEvent();
|
|
418
|
+
}, 300);
|
|
107
419
|
}
|
|
108
420
|
// Initialize canvas pool for better performance
|
|
109
421
|
this.initializeCanvasPool();
|
|
@@ -293,6 +605,35 @@ export class JaakStamps {
|
|
|
293
605
|
this.completeProcess(true);
|
|
294
606
|
}
|
|
295
607
|
}
|
|
608
|
+
async getCameraInfo() {
|
|
609
|
+
return {
|
|
610
|
+
availableCameras: this.availableCameras.map(camera => ({
|
|
611
|
+
id: camera.deviceId,
|
|
612
|
+
label: camera.label || 'Unknown Camera',
|
|
613
|
+
selected: camera.deviceId === this.selectedCameraId
|
|
614
|
+
})),
|
|
615
|
+
selectedCameraId: this.selectedCameraId,
|
|
616
|
+
deviceType: this.deviceType,
|
|
617
|
+
isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
|
|
618
|
+
preferredFacing: this.preferredCameraFacing,
|
|
619
|
+
userPreferredCamera: this.preferredCamera
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
async setPreferredCamera(camera) {
|
|
623
|
+
this.preferredCamera = camera;
|
|
624
|
+
this.debugLog('🎯 Camera preference changed to:', camera);
|
|
625
|
+
// Re-detect and apply new camera preference
|
|
626
|
+
await this.enumerateAndDetectCameras();
|
|
627
|
+
// If video is active, switch to the new preferred camera
|
|
628
|
+
if (this.isVideoActive && this.selectedCameraId) {
|
|
629
|
+
await this.switchCamera(this.selectedCameraId);
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
success: true,
|
|
633
|
+
selectedCamera: this.selectedCameraId,
|
|
634
|
+
availableCameras: this.availableCameras.length
|
|
635
|
+
};
|
|
636
|
+
}
|
|
296
637
|
async loadMobileNetModel() {
|
|
297
638
|
try {
|
|
298
639
|
this.debugLog('🤖 Loading MobileNet model...');
|
|
@@ -409,18 +750,38 @@ export class JaakStamps {
|
|
|
409
750
|
}
|
|
410
751
|
async getMaxResolution() {
|
|
411
752
|
try {
|
|
412
|
-
//
|
|
753
|
+
// Build constraints with selected camera if available
|
|
754
|
+
const videoConstraints = {};
|
|
755
|
+
if (this.selectedCameraId) {
|
|
756
|
+
videoConstraints.deviceId = { exact: this.selectedCameraId };
|
|
757
|
+
}
|
|
758
|
+
else if (this.preferredCameraFacing) {
|
|
759
|
+
videoConstraints.facingMode = this.preferredCameraFacing;
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
videoConstraints.facingMode = "environment";
|
|
763
|
+
}
|
|
764
|
+
// Get temporary stream to access capabilities
|
|
413
765
|
const tempStream = await navigator.mediaDevices.getUserMedia({
|
|
414
|
-
video:
|
|
766
|
+
video: videoConstraints
|
|
415
767
|
});
|
|
416
768
|
const videoTrack = tempStream.getVideoTracks()[0];
|
|
417
769
|
const capabilities = videoTrack.getCapabilities();
|
|
418
770
|
// Detener el stream temporal
|
|
419
771
|
tempStream.getTracks().forEach(track => track.stop());
|
|
420
|
-
//
|
|
421
|
-
const constraints = {
|
|
422
|
-
|
|
423
|
-
|
|
772
|
+
// Build constraints with resolution optimized for tablets
|
|
773
|
+
const constraints = {};
|
|
774
|
+
// Set camera selection constraints
|
|
775
|
+
if (this.selectedCameraId) {
|
|
776
|
+
constraints.deviceId = { exact: this.selectedCameraId };
|
|
777
|
+
}
|
|
778
|
+
else if (this.preferredCameraFacing) {
|
|
779
|
+
constraints.facingMode = this.preferredCameraFacing;
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
constraints.facingMode = "environment";
|
|
783
|
+
}
|
|
784
|
+
;
|
|
424
785
|
if (capabilities.width && capabilities.height) {
|
|
425
786
|
// Limitar resolución máxima para tablets para evitar problemas de rendimiento
|
|
426
787
|
const maxWidth = Math.min(capabilities.width.max, 1920);
|
|
@@ -447,13 +808,23 @@ export class JaakStamps {
|
|
|
447
808
|
}
|
|
448
809
|
catch (err) {
|
|
449
810
|
this.debugLog('⚠️ Could not get capabilities, using fallback');
|
|
450
|
-
//
|
|
811
|
+
// Optimized fallback for tablets
|
|
451
812
|
const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
|
|
452
|
-
|
|
453
|
-
facingMode: "environment",
|
|
813
|
+
const fallbackConstraints = {
|
|
454
814
|
width: { ideal: isTablet ? 1280 : 1920 },
|
|
455
815
|
height: { ideal: isTablet ? 720 : 1080 }
|
|
456
816
|
};
|
|
817
|
+
// Add camera selection to fallback
|
|
818
|
+
if (this.selectedCameraId) {
|
|
819
|
+
fallbackConstraints.deviceId = { exact: this.selectedCameraId };
|
|
820
|
+
}
|
|
821
|
+
else if (this.preferredCameraFacing) {
|
|
822
|
+
fallbackConstraints.facingMode = this.preferredCameraFacing;
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
fallbackConstraints.facingMode = "environment";
|
|
826
|
+
}
|
|
827
|
+
return fallbackConstraints;
|
|
457
828
|
}
|
|
458
829
|
}
|
|
459
830
|
async setupCamera() {
|
|
@@ -488,8 +859,7 @@ export class JaakStamps {
|
|
|
488
859
|
}
|
|
489
860
|
catch (err) {
|
|
490
861
|
this.debugLog("❌ No se pudo acceder a la cámara:", err);
|
|
491
|
-
this.
|
|
492
|
-
this.statusColor = "#ff6b6b";
|
|
862
|
+
this.handleCameraPermissionError(err);
|
|
493
863
|
}
|
|
494
864
|
}
|
|
495
865
|
preprocess(video) {
|
|
@@ -534,28 +904,48 @@ export class JaakStamps {
|
|
|
534
904
|
// Check if model is already preloaded
|
|
535
905
|
if (!this.session) {
|
|
536
906
|
this.isLoading = true;
|
|
537
|
-
this.
|
|
538
|
-
|
|
907
|
+
if (this.debug) {
|
|
908
|
+
this.statusMessage = "Cargando modelo de detección...";
|
|
909
|
+
this.statusColor = "#007bff";
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
this.statusMessage = "Cargando modelos...";
|
|
913
|
+
this.statusColor = "#007bff";
|
|
914
|
+
}
|
|
539
915
|
const modelPath = this.MODEL_PATH;
|
|
540
916
|
this.debugLog('🤖 Loading detection model:', modelPath);
|
|
541
917
|
const sessionOptions = this.getSessionOptions();
|
|
542
918
|
this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
|
|
543
919
|
// Load MobileNet model if classification is enabled and not already loaded
|
|
544
920
|
if (this.useDocumentClassification && !this.mobileNetSession) {
|
|
921
|
+
if (this.debug) {
|
|
922
|
+
this.statusMessage = "Cargando modelo de clasificación...";
|
|
923
|
+
}
|
|
545
924
|
await this.loadMobileNetModel();
|
|
546
925
|
}
|
|
547
926
|
this.isModelPreloaded = true;
|
|
927
|
+
if (this.debug) {
|
|
928
|
+
this.statusMessage = "Modelos cargados exitosamente";
|
|
929
|
+
}
|
|
548
930
|
this.emitReadyEvent();
|
|
549
931
|
}
|
|
550
932
|
else {
|
|
551
933
|
this.debugLog('🚀 Using preloaded models');
|
|
552
|
-
this.
|
|
553
|
-
|
|
934
|
+
if (this.debug) {
|
|
935
|
+
this.statusMessage = "Usando modelos precargados...";
|
|
936
|
+
this.statusColor = "#007bff";
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (this.debug) {
|
|
940
|
+
this.statusMessage = "Configurando cámara...";
|
|
554
941
|
}
|
|
555
|
-
this.statusMessage = "Configurando cámara...";
|
|
556
942
|
await this.setupCamera();
|
|
557
943
|
this.startTime = Date.now();
|
|
558
944
|
this.isLoading = false;
|
|
945
|
+
if (this.debug) {
|
|
946
|
+
this.statusMessage = "Detección activa - Busque su identificación";
|
|
947
|
+
this.statusColor = "#28a745";
|
|
948
|
+
}
|
|
559
949
|
this.detectFrame();
|
|
560
950
|
}
|
|
561
951
|
catch (err) {
|
|
@@ -1027,8 +1417,14 @@ export class JaakStamps {
|
|
|
1027
1417
|
this.videoStream.getTracks().forEach(track => track.stop());
|
|
1028
1418
|
this.videoStream = undefined;
|
|
1029
1419
|
this.isVideoActive = false;
|
|
1030
|
-
this.
|
|
1031
|
-
|
|
1420
|
+
if (this.debug) {
|
|
1421
|
+
this.statusMessage = "Cámara desactivada - Listo para nueva sesión";
|
|
1422
|
+
this.statusColor = "#6c757d";
|
|
1423
|
+
}
|
|
1424
|
+
else {
|
|
1425
|
+
this.statusMessage = "Presione el botón para activar la cámara";
|
|
1426
|
+
this.statusColor = "#aaa";
|
|
1427
|
+
}
|
|
1032
1428
|
}
|
|
1033
1429
|
this.isLoading = false;
|
|
1034
1430
|
// Limpiar canvas al finalizar sesión
|
|
@@ -1039,7 +1435,20 @@ export class JaakStamps {
|
|
|
1039
1435
|
this.cleanup();
|
|
1040
1436
|
}
|
|
1041
1437
|
render() {
|
|
1042
|
-
return (h("div", { key: '
|
|
1438
|
+
return (h("div", { key: '86ff6966e69804bea4faef5a9201480bd7ef5b9a', class: "detector-container" }, h("div", { key: 'aaed4a078d4eff98674f2163a56a1596782612bd', class: "video-container" }, h("video", { key: '6ae6a7c626c9b2b6e6915c826517d1365205b3a0', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), h("canvas", { key: 'c8666e4b4814e633155be738e23ada098fb3be93', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (h("div", { key: '968bcd21315afcf054f881647723299e7e57c29b', class: "overlay-mask" }, h("div", { key: '77f8a331f3d9e19b35c11b6c23b06420cbab9fd5', class: "card-outline" }, h("div", { key: '9d0164e7b2dcf6392471151c236127cd57507512', class: "side side-top" }), h("div", { key: '05a003eb49d25c72644aea2511b6519c5241ffb4', class: "side side-right" }), h("div", { key: 'a561ae9a94eb78aacfc24bac717164b3e22f41e5', class: "side side-bottom" }), h("div", { key: '81d64e5b58032c14062fc8efdee417db7f8a9f12', class: "side side-left" }), h("div", { key: '03c02e2c4c73c525965e34c7c47bb74f769d577f', class: "corner corner-tl" }), h("div", { key: '84780aec021ec4e0c7d38f6a659be765c4604ed5', class: "corner corner-tr" }), h("div", { key: '1ea67789c5a38cbc790f911f8f06b55c866d3927', class: "corner corner-bl" }), h("div", { key: 'e0e1999b0c5c37451850203eed4dbc2ff5834261', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (h("div", { key: '4909eb28d62b1674436c148ef46cd05a35ff8ec8', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (h("button", { key: '5a971c2121cf9962719ad89fc45530b8acccb550', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), this.isVideoActive && (h("div", { key: '109e5885c49212f89d5678d9c9716ebd48614126', class: "camera-controls" }, this.isMultipleCamerasAvailable && (h("button", { key: '7c8ce2cd775c0646c80378cd0ad1f70243443ddf', class: "flip-camera-button", onClick: () => this.flipCamera(), type: "button", title: "Cambiar c\u00E1mara" }, "Girar c\u00E1mara")), h("button", { key: '5d3d066ba7e317fdc33878eeb9f2f7d28c6f65dc', class: "camera-selector-button", onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara" }, "C\u00E1maras"), this.debug && (h("div", { key: '4cbc284f2efbcb90ef91ea2e064bf0aa1e7980af', style: {
|
|
1439
|
+
position: 'absolute',
|
|
1440
|
+
top: '50px',
|
|
1441
|
+
right: '0',
|
|
1442
|
+
background: 'rgba(0,0,0,0.8)',
|
|
1443
|
+
color: 'white',
|
|
1444
|
+
padding: '8px',
|
|
1445
|
+
fontSize: '10px',
|
|
1446
|
+
borderRadius: '4px',
|
|
1447
|
+
whiteSpace: 'nowrap'
|
|
1448
|
+
} }, "C\u00E1maras: ", this.availableCameras.length, h("br", { key: 'df214515289add2cc73f989bfacb59069334a0e6' }), "M\u00FAltiples: ", this.isMultipleCamerasAvailable ? 'Sí' : 'No', h("br", { key: 'aa2b1784781b5b45d24139e2b6cc4865d9f3a00c' }), "Selector: ", this.showCameraSelector ? 'Visible' : 'Oculto', h("br", { key: 'fb95b2b58c9fb89f632d85a9271b7788d45a66c0' }), "Video: ", this.isVideoActive ? 'Activo' : 'Inactivo')))), this.showCameraSelector && this.availableCameras.length > 0 && (h("div", { key: 'ec1c90c5a6c84e3ef75724e7e600a60714545608', class: "camera-selector-dropdown" }, h("div", { key: 'ea118817b6563c78b7fbf5ee66af2e38a441a8b7', class: "camera-selector-header" }, h("span", { key: '52603259d50ffb7aa6ea5753762f0f0d018b39ff' }, "Seleccionar C\u00E1mara"), h("button", { key: '422ae0643f5d1451cf539b3a77becbf44904a484', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'd5acef7ba0b1645c52fb3ea7b3c64e70fa187a5f', class: "camera-list" }, this.availableCameras.map((camera) => (h("button", { key: camera.deviceId, class: `camera-option ${this.selectedCameraId === camera.deviceId ? 'selected' : ''}`, onClick: () => {
|
|
1449
|
+
this.switchCamera(camera.deviceId);
|
|
1450
|
+
this.toggleCameraSelector();
|
|
1451
|
+
}, type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${this.availableCameras.indexOf(camera) + 1}`), this.selectedCameraId === camera.deviceId && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: 'b670e7bf441559bf156874dbe3bbf83dbff2fa0d', class: "device-info" }, h("small", { key: '4d333925c1ff4197b6009414ed694b0a0eb06159' }, "Dispositivo: ", this.deviceType)))))), this.isCapturing && (h("div", { key: '21f7ea0e50575226daba8607ab4bccc5029aa179', class: "capture-animation" })), this.showFlipAnimation && (h("div", { key: 'c2350cc43d3a119391f62f2cf771ed5afcdb3558', class: "flip-animation" }, h("div", { key: 'a91de9e8662a4750555bce2a879b89f9216b8156', class: "id-card-icon" }), h("div", { key: 'def1c2534e1aae27e262deb6fd26a73ff0c8cb9b', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (h("div", { key: 'acbbfb6d07e4f6bf343ff18af2d55979e76db3d4', class: "success-animation" }, h("div", { key: 'f5acd84325226e6b1ede1180335d7495a6b2d95e', class: "check-icon" }), h("div", { key: '393fd8300ce48e59bd3ef94f0e895abc3a938c1f', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (h("div", { key: 'ce0d1baebab8bf0e18531a26b549f38e349a0d72', class: "loading-overlay" }, h("div", { key: '14c76fa00ddd814f37bd9c6dc57f761119886566', class: "loading-spinner" }), h("div", { key: '87ccb288f927e61ec126d83f44061699810c9e06', class: "loading-text" }, this.statusMessage))), this.debug && (h("div", { key: '943dff6e2772f2595a95116e8943866f97cada6e', class: "status-bar" }, h("div", { key: '00fe53b71ef94d51636259f106e4518df910848d', class: "status-message", style: { 'color': this.statusColor } }, this.statusMessage))), h("div", { key: '4c3bd150006473d9540fb1418f11f45fb96d1e56', class: "watermark" }, h("img", { key: 'c634701f52a4422c7be5b4007bf34ba4aeb4ebcc', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
1043
1452
|
}
|
|
1044
1453
|
static get is() { return "jaak-stamps"; }
|
|
1045
1454
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1154,6 +1563,26 @@ export class JaakStamps {
|
|
|
1154
1563
|
"setter": false,
|
|
1155
1564
|
"reflect": false,
|
|
1156
1565
|
"defaultValue": "false"
|
|
1566
|
+
},
|
|
1567
|
+
"preferredCamera": {
|
|
1568
|
+
"type": "string",
|
|
1569
|
+
"attribute": "preferred-camera",
|
|
1570
|
+
"mutable": false,
|
|
1571
|
+
"complexType": {
|
|
1572
|
+
"original": "'auto' | 'front' | 'back'",
|
|
1573
|
+
"resolved": "\"auto\" | \"back\" | \"front\"",
|
|
1574
|
+
"references": {}
|
|
1575
|
+
},
|
|
1576
|
+
"required": false,
|
|
1577
|
+
"optional": false,
|
|
1578
|
+
"docs": {
|
|
1579
|
+
"tags": [],
|
|
1580
|
+
"text": ""
|
|
1581
|
+
},
|
|
1582
|
+
"getter": false,
|
|
1583
|
+
"setter": false,
|
|
1584
|
+
"reflect": false,
|
|
1585
|
+
"defaultValue": "'auto'"
|
|
1157
1586
|
}
|
|
1158
1587
|
};
|
|
1159
1588
|
}
|
|
@@ -1176,7 +1605,11 @@ export class JaakStamps {
|
|
|
1176
1605
|
"isDetectionPaused": {},
|
|
1177
1606
|
"isLoading": {},
|
|
1178
1607
|
"isModelPreloaded": {},
|
|
1179
|
-
"isMaskReady": {}
|
|
1608
|
+
"isMaskReady": {},
|
|
1609
|
+
"availableCameras": {},
|
|
1610
|
+
"selectedCameraId": {},
|
|
1611
|
+
"showCameraSelector": {},
|
|
1612
|
+
"isMultipleCamerasAvailable": {}
|
|
1180
1613
|
};
|
|
1181
1614
|
}
|
|
1182
1615
|
static get events() {
|
|
@@ -1349,6 +1782,44 @@ export class JaakStamps {
|
|
|
1349
1782
|
"text": "",
|
|
1350
1783
|
"tags": []
|
|
1351
1784
|
}
|
|
1785
|
+
},
|
|
1786
|
+
"getCameraInfo": {
|
|
1787
|
+
"complexType": {
|
|
1788
|
+
"signature": "() => Promise<{ availableCameras: { id: string; label: string; selected: boolean; }[]; selectedCameraId: string; deviceType: \"mobile\" | \"desktop\" | \"tablet\"; isMultipleCamerasAvailable: boolean; preferredFacing: \"environment\" | \"user\"; userPreferredCamera: \"auto\" | \"front\" | \"back\"; }>",
|
|
1789
|
+
"parameters": [],
|
|
1790
|
+
"references": {
|
|
1791
|
+
"Promise": {
|
|
1792
|
+
"location": "global",
|
|
1793
|
+
"id": "global::Promise"
|
|
1794
|
+
}
|
|
1795
|
+
},
|
|
1796
|
+
"return": "Promise<{ availableCameras: { id: string; label: string; selected: boolean; }[]; selectedCameraId: string; deviceType: \"mobile\" | \"desktop\" | \"tablet\"; isMultipleCamerasAvailable: boolean; preferredFacing: \"environment\" | \"user\"; userPreferredCamera: \"auto\" | \"front\" | \"back\"; }>"
|
|
1797
|
+
},
|
|
1798
|
+
"docs": {
|
|
1799
|
+
"text": "",
|
|
1800
|
+
"tags": []
|
|
1801
|
+
}
|
|
1802
|
+
},
|
|
1803
|
+
"setPreferredCamera": {
|
|
1804
|
+
"complexType": {
|
|
1805
|
+
"signature": "(camera: \"auto\" | \"front\" | \"back\") => Promise<{ success: boolean; selectedCamera: string; availableCameras: number; }>",
|
|
1806
|
+
"parameters": [{
|
|
1807
|
+
"name": "camera",
|
|
1808
|
+
"type": "\"auto\" | \"front\" | \"back\"",
|
|
1809
|
+
"docs": ""
|
|
1810
|
+
}],
|
|
1811
|
+
"references": {
|
|
1812
|
+
"Promise": {
|
|
1813
|
+
"location": "global",
|
|
1814
|
+
"id": "global::Promise"
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
1817
|
+
"return": "Promise<{ success: boolean; selectedCamera: string; availableCameras: number; }>"
|
|
1818
|
+
},
|
|
1819
|
+
"docs": {
|
|
1820
|
+
"text": "",
|
|
1821
|
+
"tags": []
|
|
1822
|
+
}
|
|
1352
1823
|
}
|
|
1353
1824
|
};
|
|
1354
1825
|
}
|