@jaak.ai/stamps 2.0.0-dev.58 → 2.0.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.
Files changed (44) hide show
  1. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  2. package/dist/cjs/jaak-stamps.cjs.entry.js +611 -546
  3. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  4. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/collection/components/my-component/my-component.css +2 -14
  7. package/dist/collection/components/my-component/my-component.js +237 -346
  8. package/dist/collection/components/my-component/my-component.js.map +1 -1
  9. package/dist/collection/services/CameraService.js +344 -156
  10. package/dist/collection/services/CameraService.js.map +1 -1
  11. package/dist/collection/services/DetectionService.js +38 -53
  12. package/dist/collection/services/DetectionService.js.map +1 -1
  13. package/dist/collection/services/EventBusService.js +1 -1
  14. package/dist/collection/services/EventBusService.js.map +1 -1
  15. package/dist/collection/services/LoggerService.js +40 -0
  16. package/dist/collection/services/LoggerService.js.map +1 -0
  17. package/dist/collection/services/ServiceContainer.js +12 -2
  18. package/dist/collection/services/ServiceContainer.js.map +1 -1
  19. package/dist/collection/services/interfaces/ICameraService.js.map +1 -1
  20. package/dist/collection/services/interfaces/ILogger.js +2 -0
  21. package/dist/collection/services/interfaces/ILogger.js.map +1 -0
  22. package/dist/collection/types/component-types.js.map +1 -1
  23. package/dist/components/jaak-stamps.js +614 -548
  24. package/dist/components/jaak-stamps.js.map +1 -1
  25. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  26. package/dist/esm/jaak-stamps.entry.js +611 -546
  27. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  28. package/dist/esm/loader.js +1 -1
  29. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  30. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  31. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js +2 -0
  32. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js.map +1 -0
  33. package/dist/types/components/my-component/my-component.d.ts +17 -62
  34. package/dist/types/components.d.ts +8 -6
  35. package/dist/types/services/CameraService.d.ts +14 -12
  36. package/dist/types/services/DetectionService.d.ts +3 -9
  37. package/dist/types/services/LoggerService.d.ts +12 -0
  38. package/dist/types/services/ServiceContainer.d.ts +2 -0
  39. package/dist/types/services/interfaces/ICameraService.d.ts +12 -3
  40. package/dist/types/services/interfaces/ILogger.d.ts +8 -0
  41. package/dist/types/types/component-types.d.ts +0 -3
  42. package/package.json +4 -4
  43. package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js +0 -2
  44. package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js.map +0 -1
@@ -1,18 +1,14 @@
1
1
  export class CameraService {
2
+ logger;
2
3
  eventBus;
3
4
  availableCameras = [];
4
5
  selectedCameraId = null;
5
6
  deviceType = 'desktop';
6
7
  preferredCameraFacing = null;
7
8
  preferredCamera = 'auto';
8
- // Optimization: Static caches for performance
9
- static cameraCapabilitiesCache = new Map();
10
- static deviceEnumerationCache = null;
11
- static CACHE_DURATION = 30000; // 30 seconds
12
- // Stream management for optimization
13
- currentStreamPromise;
14
- lastStreamConstraints;
15
- constructor(eventBus, preferredCamera = 'auto') {
9
+ isManuallySelected = false;
10
+ constructor(logger, eventBus, preferredCamera = 'auto') {
11
+ this.logger = logger;
16
12
  this.eventBus = eventBus;
17
13
  this.preferredCamera = preferredCamera;
18
14
  }
@@ -29,21 +25,18 @@ export class CameraService {
29
25
  else {
30
26
  this.deviceType = 'desktop';
31
27
  }
28
+ this.logger.state('DISPOSITIVO_DETECTADO', {
29
+ deviceType: this.deviceType,
30
+ userAgent: navigator.userAgent,
31
+ screenDimensions: { width: window.innerWidth, height: window.innerHeight }
32
+ });
32
33
  return this.deviceType;
33
34
  }
34
35
  async enumerateDevices() {
35
36
  try {
36
- // Check cache first for optimization
37
- const now = Date.now();
38
- if (CameraService.deviceEnumerationCache &&
39
- (now - CameraService.deviceEnumerationCache.timestamp) < CameraService.CACHE_DURATION) {
40
- this.availableCameras = CameraService.deviceEnumerationCache.devices;
41
- await this.setInitialCameraPreference();
42
- this.eventBus.emit('camera-changed', this.selectedCameraId);
43
- return this.availableCameras;
44
- }
45
37
  const permissionStatus = await this.checkCameraPermission();
46
38
  if (permissionStatus === 'denied') {
39
+ this.logger.error('Permiso de cámara denegado por el usuario');
47
40
  return [];
48
41
  }
49
42
  if (permissionStatus === 'prompt') {
@@ -52,16 +45,24 @@ export class CameraService {
52
45
  }
53
46
  const devices = await navigator.mediaDevices.enumerateDevices();
54
47
  this.availableCameras = devices.filter(device => device.kind === 'videoinput');
55
- // Cache the results for optimization
56
- CameraService.deviceEnumerationCache = {
57
- devices: this.availableCameras,
58
- timestamp: now
59
- };
60
- await this.setInitialCameraPreference();
48
+ this.logger.state('CAMARAS_DETECTADAS', {
49
+ count: this.availableCameras.length,
50
+ cameras: this.availableCameras.map(cam => ({
51
+ id: cam.deviceId,
52
+ label: cam.label || 'Unknown Camera'
53
+ }))
54
+ });
55
+ this.setInitialCameraPreference();
56
+ this.logger.state('PREFERENCIA_INICIAL_APLICADA', {
57
+ selectedCameraId: this.selectedCameraId,
58
+ preferredCameraFacing: this.preferredCameraFacing,
59
+ preferredCamera: this.preferredCamera
60
+ });
61
61
  this.eventBus.emit('camera-changed', this.selectedCameraId);
62
62
  return this.availableCameras;
63
63
  }
64
64
  catch (error) {
65
+ this.logger.error('Error al enumerar cámaras disponibles:', error);
65
66
  this.handleCameraPermissionError(error);
66
67
  return [];
67
68
  }
@@ -82,6 +83,8 @@ export class CameraService {
82
83
  }
83
84
  this.selectedCameraId = cameraId;
84
85
  this.updatePreferredFacing(camera);
86
+ this.isManuallySelected = true; // Mark as manually selected
87
+ this.savePreference();
85
88
  this.eventBus.emit('camera-changed', cameraId);
86
89
  }
87
90
  getPreferredFacing() {
@@ -90,20 +93,14 @@ export class CameraService {
90
93
  async setupCamera(constraints) {
91
94
  try {
92
95
  const finalConstraints = constraints || await this.getMaxResolution();
93
- // Optimization: Check if we can reuse existing stream with same constraints
94
- if (this.currentStreamPromise && this.constraintsEqual(finalConstraints, this.lastStreamConstraints)) {
95
- return await this.currentStreamPromise;
96
- }
97
- // Create new stream promise for lazy loading
98
- this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
96
+ const stream = await navigator.mediaDevices.getUserMedia({
99
97
  video: finalConstraints,
100
98
  audio: false
101
99
  });
102
- this.lastStreamConstraints = finalConstraints;
103
- const stream = await this.currentStreamPromise;
104
100
  return stream;
105
101
  }
106
102
  catch (error) {
103
+ this.logger.error('Error en setupCamera, intentando con restricciones básicas:', error);
107
104
  // Fallback to basic constraints if getMaxResolution fails
108
105
  const basicConstraints = {
109
106
  width: { ideal: 1280 },
@@ -113,21 +110,65 @@ export class CameraService {
113
110
  if (this.deviceType !== 'desktop' && this.preferredCameraFacing) {
114
111
  basicConstraints.facingMode = this.preferredCameraFacing;
115
112
  }
116
- this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
113
+ this.logger.state('USANDO_RESTRICCIONES_BASICAS', { constraints: basicConstraints });
114
+ return await navigator.mediaDevices.getUserMedia({
117
115
  video: basicConstraints,
118
116
  audio: false
119
117
  });
120
- this.lastStreamConstraints = basicConstraints;
121
- return await this.currentStreamPromise;
122
118
  }
123
119
  }
124
120
  async switchCamera(cameraId) {
125
121
  const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
126
122
  if (!selectedCamera) {
123
+ this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
127
124
  await this.enumerateDevices();
128
125
  return;
129
126
  }
130
127
  await this.setSelectedCamera(cameraId);
128
+ this.logger.state('CAMARA_CAMBIADA', {
129
+ label: selectedCamera.label,
130
+ deviceId: selectedCamera.deviceId,
131
+ isManuallySelected: this.isManuallySelected
132
+ });
133
+ }
134
+ async flipToNextCamera() {
135
+ if (!this.isMultipleCamerasAvailable())
136
+ return;
137
+ const currentIndex = this.availableCameras.findIndex(camera => camera.deviceId === this.selectedCameraId);
138
+ const nextIndex = (currentIndex + 1) % this.availableCameras.length;
139
+ const nextCamera = this.availableCameras[nextIndex];
140
+ await this.switchCamera(nextCamera.deviceId);
141
+ }
142
+ savePreference() {
143
+ try {
144
+ const preference = {
145
+ cameraId: this.selectedCameraId,
146
+ facing: this.preferredCameraFacing,
147
+ timestamp: Date.now()
148
+ };
149
+ localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
150
+ this.logger.state('PREFERENCIA_CAMARA_GUARDADA', preference);
151
+ }
152
+ catch (error) {
153
+ this.logger.warn('Error al guardar preferencia de cámara:', error);
154
+ }
155
+ }
156
+ loadPreference() {
157
+ try {
158
+ const saved = localStorage.getItem('jaak-stamps-camera-preference');
159
+ if (saved) {
160
+ const preference = JSON.parse(saved);
161
+ const isStillAvailable = this.availableCameras.some(camera => camera.deviceId === preference.cameraId);
162
+ if (isStillAvailable) {
163
+ this.selectedCameraId = preference.cameraId;
164
+ this.preferredCameraFacing = preference.facing;
165
+ this.logger.state('PREFERENCIA_CAMARA_CARGADA', preference);
166
+ }
167
+ }
168
+ }
169
+ catch (error) {
170
+ this.logger.warn('Error al cargar preferencia de cámara:', error);
171
+ }
131
172
  }
132
173
  isRearCamera(stream) {
133
174
  const videoTrack = stream.getVideoTracks()[0];
@@ -136,18 +177,13 @@ export class CameraService {
136
177
  const settings = videoTrack.getSettings();
137
178
  return settings.facingMode === 'environment';
138
179
  }
139
- async getCameraInfo() {
140
- const availableCamerasWithAutofocus = await Promise.all(this.availableCameras.map(async (camera) => {
141
- const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
142
- return {
180
+ getCameraInfo() {
181
+ return {
182
+ availableCameras: this.availableCameras.map(camera => ({
143
183
  id: camera.deviceId,
144
184
  label: camera.label || 'Unknown Camera',
145
- selected: camera.deviceId === this.selectedCameraId,
146
- hasAutofocus
147
- };
148
- }));
149
- return {
150
- availableCameras: availableCamerasWithAutofocus,
185
+ selected: camera.deviceId === this.selectedCameraId
186
+ })),
151
187
  selectedCameraId: this.selectedCameraId,
152
188
  deviceType: this.deviceType,
153
189
  isMultipleCamerasAvailable: this.isMultipleCamerasAvailable(),
@@ -163,6 +199,7 @@ export class CameraService {
163
199
  return permission.state;
164
200
  }
165
201
  catch (error) {
202
+ this.logger.warn('No se pudo verificar permisos de cámara:', error);
166
203
  return 'prompt';
167
204
  }
168
205
  }
@@ -170,48 +207,35 @@ export class CameraService {
170
207
  this.availableCameras = [];
171
208
  this.eventBus.emit('error', new Error(`Camera permission error: ${error.message}`));
172
209
  }
173
- async setInitialCameraPreference() {
210
+ setInitialCameraPreference() {
174
211
  if (this.availableCameras.length === 0)
175
212
  return;
213
+ // Clear any existing localStorage preferences to ensure fresh start
214
+ localStorage.removeItem('jaak-stamps-camera-preference');
215
+ this.logger.state('APLICANDO_PREFERENCIA_INICIAL', {
216
+ preferredCamera: this.preferredCamera,
217
+ availableCamerasCount: this.availableCameras.length
218
+ });
219
+ this.isManuallySelected = false; // Reset manual selection flag
176
220
  if (this.preferredCamera === 'front') {
177
- await this.selectFrontCamera();
221
+ this.selectFrontCamera();
178
222
  }
179
223
  else if (this.preferredCamera === 'back') {
180
- await this.selectBackCamera();
224
+ this.selectBackCamera();
181
225
  }
182
226
  else {
183
- await this.selectAutoCamera();
184
- }
185
- }
186
- async checkCameraAutofocus(deviceId) {
187
- try {
188
- // Check cache first for optimization
189
- if (CameraService.cameraCapabilitiesCache.has(deviceId)) {
190
- const cached = CameraService.cameraCapabilitiesCache.get(deviceId);
191
- return typeof cached === 'boolean' ? cached : false;
192
- }
193
- const stream = await navigator.mediaDevices.getUserMedia({
194
- video: { deviceId: { exact: deviceId } }
195
- });
196
- const videoTrack = stream.getVideoTracks()[0];
197
- const capabilities = videoTrack.getCapabilities();
198
- stream.getTracks().forEach(track => track.stop());
199
- const hasFocusMode = capabilities.focusMode && Array.isArray(capabilities.focusMode) && capabilities.focusMode.length > 0;
200
- const hasAutofocus = hasFocusMode && (capabilities.focusMode.includes('continuous') ||
201
- capabilities.focusMode.includes('auto'));
202
- // Cache the result for future use
203
- CameraService.cameraCapabilitiesCache.set(deviceId, hasAutofocus);
204
- return hasAutofocus;
205
- }
206
- catch (error) {
207
- // Cache negative result to avoid repeated failures
208
- CameraService.cameraCapabilitiesCache.set(deviceId, false);
209
- return false;
227
+ this.selectAutoCamera();
210
228
  }
211
229
  }
212
- async selectFrontCamera() {
230
+ selectFrontCamera() {
213
231
  this.preferredCameraFacing = 'user';
214
- const frontCameras = this.availableCameras.filter(camera => {
232
+ this.logger.state('BUSCANDO_CAMARA_FRONTAL', {
233
+ availableCameras: this.availableCameras.map(cam => ({
234
+ id: cam.deviceId,
235
+ label: cam.label
236
+ }))
237
+ });
238
+ const frontCamera = this.availableCameras.find(camera => {
215
239
  const label = camera.label.toLowerCase();
216
240
  return label.includes('front') ||
217
241
  label.includes('user') ||
@@ -219,24 +243,24 @@ export class CameraService {
219
243
  label.includes('frontal') ||
220
244
  (!label.includes('back') && !label.includes('rear') && !label.includes('environment'));
221
245
  });
222
- let selectedCamera = null;
223
- if (frontCameras.length > 0) {
224
- for (const camera of frontCameras) {
225
- const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
226
- if (hasAutofocus) {
227
- selectedCamera = camera;
228
- break;
229
- }
230
- }
231
- if (!selectedCamera) {
232
- selectedCamera = frontCameras[0];
233
- }
234
- }
235
- this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
246
+ // If not found, use the first camera (usually front on mobile)
247
+ this.selectedCameraId = frontCamera ? frontCamera.deviceId : this.availableCameras[0].deviceId;
248
+ this.logger.state('CAMARA_FRONTAL_SELECCIONADA', {
249
+ found: !!frontCamera,
250
+ label: frontCamera?.label || this.availableCameras[0].label,
251
+ deviceId: this.selectedCameraId
252
+ });
236
253
  }
237
- async selectBackCamera() {
254
+ selectBackCamera() {
238
255
  this.preferredCameraFacing = 'environment';
239
- const backCameras = this.availableCameras.filter(camera => {
256
+ this.logger.state('BUSCANDO_CAMARA_TRASERA', {
257
+ availableCameras: this.availableCameras.map(cam => ({
258
+ id: cam.deviceId,
259
+ label: cam.label
260
+ }))
261
+ });
262
+ // Try to find rear camera with multiple detection methods
263
+ let backCamera = this.availableCameras.find(camera => {
240
264
  const label = camera.label.toLowerCase();
241
265
  return label.includes('back') ||
242
266
  label.includes('rear') ||
@@ -244,33 +268,34 @@ export class CameraService {
244
268
  label.includes('trasera') ||
245
269
  label.includes('posterior');
246
270
  });
247
- let selectedCamera = null;
248
- if (backCameras.length > 0) {
249
- for (const camera of backCameras) {
250
- const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
251
- if (hasAutofocus) {
252
- selectedCamera = camera;
253
- break;
254
- }
255
- }
256
- if (!selectedCamera) {
257
- selectedCamera = backCameras[0];
258
- }
259
- }
260
- else if (this.availableCameras.length > 1) {
261
- const lastCamera = this.availableCameras[this.availableCameras.length - 1];
262
- const hasAutofocus = await this.checkCameraAutofocus(lastCamera.deviceId);
263
- selectedCamera = hasAutofocus ? lastCamera : this.availableCameras[this.availableCameras.length - 1];
271
+ // If not found by label, try to use the last camera (usually rear on mobile)
272
+ if (!backCamera && this.availableCameras.length > 1) {
273
+ backCamera = this.availableCameras[this.availableCameras.length - 1];
274
+ this.logger.state('USANDO_ULTIMA_CAMARA_COMO_TRASERA', {
275
+ label: backCamera.label,
276
+ deviceId: backCamera.deviceId
277
+ });
264
278
  }
265
- this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
279
+ this.selectedCameraId = backCamera ? backCamera.deviceId : this.availableCameras[0].deviceId;
280
+ this.logger.state('CAMARA_TRASERA_SELECCIONADA', {
281
+ found: !!backCamera,
282
+ label: backCamera?.label || this.availableCameras[0].label,
283
+ deviceId: this.selectedCameraId
284
+ });
266
285
  }
267
- async selectAutoCamera() {
286
+ selectAutoCamera() {
268
287
  if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
269
- await this.selectBackCamera();
288
+ this.selectBackCamera();
289
+ this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
270
290
  }
271
291
  else {
272
292
  // For desktop, prefer front camera (usually built-in webcam)
273
- await this.selectFrontCamera();
293
+ this.selectFrontCamera();
294
+ this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', {
295
+ type: 'front',
296
+ label: this.availableCameras[0].label,
297
+ deviceId: this.selectedCameraId
298
+ });
274
299
  }
275
300
  }
276
301
  updatePreferredFacing(camera) {
@@ -286,19 +311,40 @@ export class CameraService {
286
311
  await this.detectDeviceType();
287
312
  }
288
313
  const videoConstraints = {};
289
- // Priority 1: Always use deviceId if available (ensures specific camera with autofocus)
290
- if (this.selectedCameraId) {
314
+ this.logger.state('CONFIGURANDO_CONSTRAINS_CAMARA', {
315
+ selectedCameraId: this.selectedCameraId,
316
+ preferredCameraFacing: this.preferredCameraFacing,
317
+ preferredCamera: this.preferredCamera,
318
+ deviceType: this.deviceType,
319
+ isManuallySelected: this.isManuallySelected
320
+ });
321
+ if (this.isManuallySelected && this.selectedCameraId) {
322
+ // When manually selected, use deviceId for exact camera selection
291
323
  videoConstraints.deviceId = { exact: this.selectedCameraId };
324
+ this.logger.state('USANDO_CAMARA_MANUAL', {
325
+ deviceId: this.selectedCameraId
326
+ });
292
327
  }
293
- // Priority 2: Use facingMode as fallback when no specific camera is selected
294
328
  else if (this.preferredCameraFacing) {
329
+ // Use facingMode for initial preference-based selection
330
+ // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
295
331
  const facingConstraint = this.deviceType === 'desktop'
296
332
  ? { ideal: this.preferredCameraFacing }
297
333
  : { exact: this.preferredCameraFacing };
298
334
  videoConstraints.facingMode = facingConstraint;
335
+ this.logger.state('USANDO_FACING_CONSTRAINT', {
336
+ facingMode: this.preferredCameraFacing,
337
+ constraintType: this.deviceType === 'desktop' ? 'ideal' : 'exact'
338
+ });
339
+ }
340
+ else if (this.selectedCameraId) {
341
+ videoConstraints.deviceId = { exact: this.selectedCameraId };
342
+ this.logger.state('USANDO_CAMARA_SELECCIONADA', {
343
+ deviceId: this.selectedCameraId
344
+ });
299
345
  }
300
- // Priority 3: Fallback to preferredCamera setting
301
346
  else {
347
+ // Use preferredCamera setting to determine default facing mode
302
348
  if (this.preferredCamera === 'front') {
303
349
  videoConstraints.facingMode = "user";
304
350
  }
@@ -306,25 +352,24 @@ export class CameraService {
306
352
  videoConstraints.facingMode = "environment";
307
353
  }
308
354
  else {
355
+ // Auto mode: use environment for mobile/tablet, user for desktop
309
356
  videoConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
310
357
  }
358
+ this.logger.state('USANDO_PREFERENCIA_CONFIGURADA', {
359
+ facingMode: videoConstraints.facingMode,
360
+ preferredCamera: this.preferredCamera,
361
+ deviceType: this.deviceType
362
+ });
311
363
  }
312
- // Optimization: Use cached capabilities if available
313
- let capabilities;
314
- if (this.selectedCameraId && CameraService.cameraCapabilitiesCache.has(this.selectedCameraId + '_caps')) {
315
- capabilities = JSON.parse(CameraService.cameraCapabilitiesCache.get(this.selectedCameraId + '_caps'));
316
- }
317
- else {
318
- const tempStream = await navigator.mediaDevices.getUserMedia({ video: videoConstraints });
319
- const videoTrack = tempStream.getVideoTracks()[0];
320
- capabilities = videoTrack.getCapabilities();
321
- tempStream.getTracks().forEach(track => track.stop());
322
- // Cache capabilities for future use
323
- if (this.selectedCameraId) {
324
- CameraService.cameraCapabilitiesCache.set(this.selectedCameraId + '_caps', JSON.stringify(capabilities));
325
- }
326
- }
364
+ const tempStream = await navigator.mediaDevices.getUserMedia({
365
+ video: videoConstraints
366
+ });
367
+ const videoTrack = tempStream.getVideoTracks()[0];
368
+ const capabilities = videoTrack.getCapabilities();
369
+ tempStream.getTracks().forEach(track => track.stop());
327
370
  const constraints = { ...videoConstraints };
371
+ // Enhanced focus and image quality settings
372
+ this.applyAdvancedCameraSettings(constraints, capabilities);
328
373
  if (capabilities.width && capabilities.height) {
329
374
  const maxWidth = Math.min(capabilities.width.max, 1920);
330
375
  const maxHeight = Math.min(capabilities.height.max, 1080);
@@ -338,20 +383,15 @@ export class CameraService {
338
383
  constraints.height = { ideal: maxHeight };
339
384
  }
340
385
  }
341
- // Configure autofocus if camera has the capability
342
- const capabilitiesAny = capabilities;
343
- if (capabilitiesAny.focusMode && Array.isArray(capabilitiesAny.focusMode)) {
344
- if (capabilitiesAny.focusMode.includes('continuous')) {
345
- constraints.focusMode = 'continuous';
346
- }
347
- else if (capabilitiesAny.focusMode.includes('auto')) {
348
- constraints.focusMode = 'auto';
349
- }
350
- }
386
+ this.logger.state('CONFIGURACION_CAMARA_OPTIMIZADA', {
387
+ constraints,
388
+ capabilities: this.getCapabilitiesSummary(capabilities)
389
+ });
351
390
  return constraints;
352
391
  }
353
392
  catch (err) {
354
- // Fallback logic with minimal temporary streams
393
+ this.logger.warn('No se pudieron obtener capacidades de cámara, usando configuración de respaldo');
394
+ // Ensure device type is detected before determining constraints
355
395
  if (!this.deviceType || this.deviceType === 'desktop') {
356
396
  await this.detectDeviceType();
357
397
  }
@@ -360,16 +400,25 @@ export class CameraService {
360
400
  width: { ideal: isTablet ? 1280 : 1920 },
361
401
  height: { ideal: isTablet ? 720 : 1080 }
362
402
  };
363
- if (this.selectedCameraId) {
403
+ // Apply basic focus settings even for fallback
404
+ this.applyBasicFocusSettings(fallbackConstraints);
405
+ if (this.isManuallySelected && this.selectedCameraId) {
406
+ // When manually selected, use deviceId for exact camera selection
364
407
  fallbackConstraints.deviceId = { exact: this.selectedCameraId };
365
408
  }
366
409
  else if (this.preferredCameraFacing) {
410
+ // Use facingMode for initial preference-based selection
411
+ // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
367
412
  const facingConstraint = this.deviceType === 'desktop'
368
413
  ? { ideal: this.preferredCameraFacing }
369
414
  : { exact: this.preferredCameraFacing };
370
415
  fallbackConstraints.facingMode = facingConstraint;
371
416
  }
417
+ else if (this.selectedCameraId) {
418
+ fallbackConstraints.deviceId = { exact: this.selectedCameraId };
419
+ }
372
420
  else {
421
+ // Use preferredCamera setting to determine default facing mode
373
422
  if (this.preferredCamera === 'front') {
374
423
  fallbackConstraints.facingMode = "user";
375
424
  }
@@ -377,26 +426,165 @@ export class CameraService {
377
426
  fallbackConstraints.facingMode = "environment";
378
427
  }
379
428
  else {
429
+ // Auto mode: use environment for mobile/tablet, user for desktop
380
430
  fallbackConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
381
431
  }
382
432
  }
383
433
  return fallbackConstraints;
384
434
  }
385
435
  }
386
- // Optimization helper methods
387
- constraintsEqual(a, b) {
388
- if (!a || !b)
389
- return false;
390
- return JSON.stringify(a) === JSON.stringify(b);
436
+ applyAdvancedCameraSettings(constraints, capabilities) {
437
+ // Apply standard camera settings that are widely supported
438
+ // Frame rate optimization
439
+ if (capabilities.frameRate) {
440
+ constraints.frameRate = {
441
+ ideal: 30,
442
+ min: 15,
443
+ max: 60
444
+ };
445
+ }
446
+ // Width and height constraints for optimal resolution
447
+ if (capabilities.width && capabilities.height) {
448
+ constraints.width = {
449
+ ideal: Math.min(1920, capabilities.width.max || 1920),
450
+ min: 640
451
+ };
452
+ constraints.height = {
453
+ ideal: Math.min(1080, capabilities.height.max || 1080),
454
+ min: 480
455
+ };
456
+ }
457
+ // Apply enhanced constraints through advanced array for better browser compatibility
458
+ const advancedConstraints = [];
459
+ // Try to set optimal settings through advanced constraints
460
+ const advanced = {};
461
+ // Focus mode - use 'any' type to bypass TypeScript limitations
462
+ if (capabilities.focusMode) {
463
+ if (capabilities.focusMode.includes('continuous')) {
464
+ advanced.focusMode = 'continuous';
465
+ }
466
+ else if (capabilities.focusMode.includes('single-shot')) {
467
+ advanced.focusMode = 'single-shot';
468
+ }
469
+ }
470
+ // Focus distance for close objects
471
+ if (capabilities.focusDistance) {
472
+ advanced.focusDistance = {
473
+ ideal: 0.4, // 40cm - optimal for document capture
474
+ min: 0.2,
475
+ max: 1.0
476
+ };
477
+ }
478
+ // Zoom control
479
+ if (capabilities.zoom) {
480
+ advanced.zoom = {
481
+ ideal: 1.0,
482
+ min: capabilities.zoom.min,
483
+ max: Math.min(capabilities.zoom.max, 3.0)
484
+ };
485
+ }
486
+ // Add advanced constraints if any were set
487
+ if (Object.keys(advanced).length > 0) {
488
+ advancedConstraints.push(advanced);
489
+ }
490
+ if (advancedConstraints.length > 0) {
491
+ constraints.advanced = advancedConstraints;
492
+ }
391
493
  }
392
- // Cleanup method for optimization
393
- static clearCaches() {
394
- CameraService.cameraCapabilitiesCache.clear();
395
- CameraService.deviceEnumerationCache = null;
494
+ applyBasicFocusSettings(constraints) {
495
+ // Apply basic focus settings when capabilities are not available
496
+ // Use advanced constraints for non-standard properties
497
+ const advanced = {
498
+ focusMode: 'continuous',
499
+ exposureMode: 'continuous',
500
+ whiteBalanceMode: 'continuous'
501
+ };
502
+ if (!constraints.advanced) {
503
+ constraints.advanced = [];
504
+ }
505
+ constraints.advanced.push(advanced);
506
+ constraints.frameRate = { ideal: 30 };
396
507
  }
397
- // Method to invalidate device cache when needed
398
- invalidateDeviceCache() {
399
- CameraService.deviceEnumerationCache = null;
508
+ getCapabilitiesSummary(capabilities) {
509
+ return {
510
+ hasAutoFocus: !!capabilities.focusMode,
511
+ hasFocusDistance: !!capabilities.focusDistance,
512
+ hasExposureControl: !!capabilities.exposureMode,
513
+ hasWhiteBalance: !!capabilities.whiteBalanceMode,
514
+ hasZoom: !!capabilities.zoom,
515
+ hasTorch: capabilities.torch !== undefined,
516
+ hasImageControls: !!(capabilities.brightness || capabilities.contrast || capabilities.saturation || capabilities.sharpness),
517
+ resolutionRange: capabilities.width && capabilities.height ?
518
+ `${capabilities.width.min}x${capabilities.height.min} - ${capabilities.width.max}x${capabilities.height.max}` : 'unknown'
519
+ };
520
+ }
521
+ // New method to enable torch/flash for better illumination
522
+ async setTorchEnabled(enabled, stream) {
523
+ try {
524
+ if (!stream) {
525
+ this.logger.warn('No hay stream disponible para controlar el flash');
526
+ return false;
527
+ }
528
+ const videoTrack = stream.getVideoTracks()[0];
529
+ if (!videoTrack) {
530
+ this.logger.warn('No hay track de video disponible');
531
+ return false;
532
+ }
533
+ const capabilities = videoTrack.getCapabilities();
534
+ if (capabilities.torch === undefined) {
535
+ this.logger.warn('El dispositivo no soporta control de flash');
536
+ return false;
537
+ }
538
+ await videoTrack.applyConstraints({
539
+ advanced: [{ torch: enabled }]
540
+ });
541
+ this.logger.state('FLASH_CONTROLADO', { enabled });
542
+ return true;
543
+ }
544
+ catch (error) {
545
+ this.logger.error('Error al controlar el flash:', error);
546
+ return false;
547
+ }
548
+ }
549
+ // Method to manually trigger focus on a specific point
550
+ async focusAtPoint(x, y, stream) {
551
+ try {
552
+ if (!stream) {
553
+ this.logger.warn('No hay stream disponible para enfocar');
554
+ return false;
555
+ }
556
+ const videoTrack = stream.getVideoTracks()[0];
557
+ if (!videoTrack) {
558
+ this.logger.warn('No hay track de video disponible');
559
+ return false;
560
+ }
561
+ const capabilities = videoTrack.getCapabilities();
562
+ if (!capabilities.focusMode || !capabilities.focusMode.includes('single-shot')) {
563
+ this.logger.warn('El dispositivo no soporta enfoque manual');
564
+ return false;
565
+ }
566
+ // Trigger single-shot focus
567
+ await videoTrack.applyConstraints({
568
+ advanced: [{ focusMode: 'single-shot' }]
569
+ });
570
+ // Return to continuous focus after a short delay
571
+ setTimeout(async () => {
572
+ try {
573
+ await videoTrack.applyConstraints({
574
+ advanced: [{ focusMode: 'continuous' }]
575
+ });
576
+ }
577
+ catch (error) {
578
+ this.logger.warn('Error al volver a enfoque continuo:', error);
579
+ }
580
+ }, 1000);
581
+ this.logger.state('ENFOQUE_MANUAL_ACTIVADO', { x, y });
582
+ return true;
583
+ }
584
+ catch (error) {
585
+ this.logger.error('Error al enfocar manualmente:', error);
586
+ return false;
587
+ }
400
588
  }
401
589
  }
402
590
  //# sourceMappingURL=CameraService.js.map