@jaak.ai/stamps 2.0.0 → 2.1.0-dev.2

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 (45) hide show
  1. package/README.md +11 -1
  2. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  3. package/dist/cjs/jaak-stamps.cjs.entry.js +759 -623
  4. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  5. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  6. package/dist/cjs/loader.cjs.js +1 -1
  7. package/dist/collection/components/my-component/my-component.css +178 -11
  8. package/dist/collection/components/my-component/my-component.js +566 -250
  9. package/dist/collection/components/my-component/my-component.js.map +1 -1
  10. package/dist/collection/services/CameraService.js +156 -344
  11. package/dist/collection/services/CameraService.js.map +1 -1
  12. package/dist/collection/services/DetectionService.js +68 -38
  13. package/dist/collection/services/DetectionService.js.map +1 -1
  14. package/dist/collection/services/EventBusService.js +1 -1
  15. package/dist/collection/services/EventBusService.js.map +1 -1
  16. package/dist/collection/services/ServiceContainer.js +4 -13
  17. package/dist/collection/services/ServiceContainer.js.map +1 -1
  18. package/dist/collection/services/interfaces/ICameraService.js.map +1 -1
  19. package/dist/collection/types/component-types.js.map +1 -1
  20. package/dist/components/jaak-stamps.js +763 -626
  21. package/dist/components/jaak-stamps.js.map +1 -1
  22. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  23. package/dist/esm/jaak-stamps.entry.js +759 -623
  24. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  25. package/dist/esm/loader.js +1 -1
  26. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  27. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  28. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js +2 -0
  29. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js.map +1 -0
  30. package/dist/types/components/my-component/my-component.d.ts +68 -17
  31. package/dist/types/components.d.ts +14 -8
  32. package/dist/types/services/CameraService.d.ts +12 -14
  33. package/dist/types/services/DetectionService.d.ts +10 -3
  34. package/dist/types/services/ServiceContainer.d.ts +1 -2
  35. package/dist/types/services/interfaces/ICameraService.d.ts +3 -12
  36. package/dist/types/types/component-types.d.ts +3 -0
  37. package/package.json +2 -2
  38. package/dist/collection/services/LoggerService.js +0 -40
  39. package/dist/collection/services/LoggerService.js.map +0 -1
  40. package/dist/collection/services/interfaces/ILogger.js +0 -2
  41. package/dist/collection/services/interfaces/ILogger.js.map +0 -1
  42. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js +0 -2
  43. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js.map +0 -1
  44. package/dist/types/services/LoggerService.d.ts +0 -12
  45. package/dist/types/services/interfaces/ILogger.d.ts +0 -8
@@ -1,14 +1,18 @@
1
1
  export class CameraService {
2
- logger;
3
2
  eventBus;
4
3
  availableCameras = [];
5
4
  selectedCameraId = null;
6
5
  deviceType = 'desktop';
7
6
  preferredCameraFacing = null;
8
7
  preferredCamera = 'auto';
9
- isManuallySelected = false;
10
- constructor(logger, eventBus, preferredCamera = 'auto') {
11
- this.logger = logger;
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') {
12
16
  this.eventBus = eventBus;
13
17
  this.preferredCamera = preferredCamera;
14
18
  }
@@ -25,18 +29,21 @@ export class CameraService {
25
29
  else {
26
30
  this.deviceType = 'desktop';
27
31
  }
28
- this.logger.state('DISPOSITIVO_DETECTADO', {
29
- deviceType: this.deviceType,
30
- userAgent: navigator.userAgent,
31
- screenDimensions: { width: window.innerWidth, height: window.innerHeight }
32
- });
33
32
  return this.deviceType;
34
33
  }
35
34
  async enumerateDevices() {
36
35
  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
+ }
37
45
  const permissionStatus = await this.checkCameraPermission();
38
46
  if (permissionStatus === 'denied') {
39
- this.logger.error('Permiso de cámara denegado por el usuario');
40
47
  return [];
41
48
  }
42
49
  if (permissionStatus === 'prompt') {
@@ -45,24 +52,16 @@ export class CameraService {
45
52
  }
46
53
  const devices = await navigator.mediaDevices.enumerateDevices();
47
54
  this.availableCameras = devices.filter(device => device.kind === 'videoinput');
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
- });
55
+ // Cache the results for optimization
56
+ CameraService.deviceEnumerationCache = {
57
+ devices: this.availableCameras,
58
+ timestamp: now
59
+ };
60
+ await this.setInitialCameraPreference();
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);
66
65
  this.handleCameraPermissionError(error);
67
66
  return [];
68
67
  }
@@ -83,8 +82,6 @@ export class CameraService {
83
82
  }
84
83
  this.selectedCameraId = cameraId;
85
84
  this.updatePreferredFacing(camera);
86
- this.isManuallySelected = true; // Mark as manually selected
87
- this.savePreference();
88
85
  this.eventBus.emit('camera-changed', cameraId);
89
86
  }
90
87
  getPreferredFacing() {
@@ -93,14 +90,20 @@ export class CameraService {
93
90
  async setupCamera(constraints) {
94
91
  try {
95
92
  const finalConstraints = constraints || await this.getMaxResolution();
96
- const stream = await navigator.mediaDevices.getUserMedia({
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({
97
99
  video: finalConstraints,
98
100
  audio: false
99
101
  });
102
+ this.lastStreamConstraints = finalConstraints;
103
+ const stream = await this.currentStreamPromise;
100
104
  return stream;
101
105
  }
102
106
  catch (error) {
103
- this.logger.error('Error en setupCamera, intentando con restricciones básicas:', error);
104
107
  // Fallback to basic constraints if getMaxResolution fails
105
108
  const basicConstraints = {
106
109
  width: { ideal: 1280 },
@@ -110,65 +113,21 @@ export class CameraService {
110
113
  if (this.deviceType !== 'desktop' && this.preferredCameraFacing) {
111
114
  basicConstraints.facingMode = this.preferredCameraFacing;
112
115
  }
113
- this.logger.state('USANDO_RESTRICCIONES_BASICAS', { constraints: basicConstraints });
114
- return await navigator.mediaDevices.getUserMedia({
116
+ this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
115
117
  video: basicConstraints,
116
118
  audio: false
117
119
  });
120
+ this.lastStreamConstraints = basicConstraints;
121
+ return await this.currentStreamPromise;
118
122
  }
119
123
  }
120
124
  async switchCamera(cameraId) {
121
125
  const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
122
126
  if (!selectedCamera) {
123
- this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
124
127
  await this.enumerateDevices();
125
128
  return;
126
129
  }
127
130
  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
- }
172
131
  }
173
132
  isRearCamera(stream) {
174
133
  const videoTrack = stream.getVideoTracks()[0];
@@ -177,13 +136,18 @@ export class CameraService {
177
136
  const settings = videoTrack.getSettings();
178
137
  return settings.facingMode === 'environment';
179
138
  }
180
- getCameraInfo() {
181
- return {
182
- availableCameras: this.availableCameras.map(camera => ({
139
+ async getCameraInfo() {
140
+ const availableCamerasWithAutofocus = await Promise.all(this.availableCameras.map(async (camera) => {
141
+ const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
142
+ return {
183
143
  id: camera.deviceId,
184
144
  label: camera.label || 'Unknown Camera',
185
- selected: camera.deviceId === this.selectedCameraId
186
- })),
145
+ selected: camera.deviceId === this.selectedCameraId,
146
+ hasAutofocus
147
+ };
148
+ }));
149
+ return {
150
+ availableCameras: availableCamerasWithAutofocus,
187
151
  selectedCameraId: this.selectedCameraId,
188
152
  deviceType: this.deviceType,
189
153
  isMultipleCamerasAvailable: this.isMultipleCamerasAvailable(),
@@ -199,7 +163,6 @@ export class CameraService {
199
163
  return permission.state;
200
164
  }
201
165
  catch (error) {
202
- this.logger.warn('No se pudo verificar permisos de cámara:', error);
203
166
  return 'prompt';
204
167
  }
205
168
  }
@@ -207,35 +170,48 @@ export class CameraService {
207
170
  this.availableCameras = [];
208
171
  this.eventBus.emit('error', new Error(`Camera permission error: ${error.message}`));
209
172
  }
210
- setInitialCameraPreference() {
173
+ async setInitialCameraPreference() {
211
174
  if (this.availableCameras.length === 0)
212
175
  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
220
176
  if (this.preferredCamera === 'front') {
221
- this.selectFrontCamera();
177
+ await this.selectFrontCamera();
222
178
  }
223
179
  else if (this.preferredCamera === 'back') {
224
- this.selectBackCamera();
180
+ await this.selectBackCamera();
225
181
  }
226
182
  else {
227
- this.selectAutoCamera();
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;
228
210
  }
229
211
  }
230
- selectFrontCamera() {
212
+ async selectFrontCamera() {
231
213
  this.preferredCameraFacing = 'user';
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 => {
214
+ const frontCameras = this.availableCameras.filter(camera => {
239
215
  const label = camera.label.toLowerCase();
240
216
  return label.includes('front') ||
241
217
  label.includes('user') ||
@@ -243,24 +219,24 @@ export class CameraService {
243
219
  label.includes('frontal') ||
244
220
  (!label.includes('back') && !label.includes('rear') && !label.includes('environment'));
245
221
  });
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
- });
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;
253
236
  }
254
- selectBackCamera() {
237
+ async selectBackCamera() {
255
238
  this.preferredCameraFacing = 'environment';
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 => {
239
+ const backCameras = this.availableCameras.filter(camera => {
264
240
  const label = camera.label.toLowerCase();
265
241
  return label.includes('back') ||
266
242
  label.includes('rear') ||
@@ -268,34 +244,33 @@ export class CameraService {
268
244
  label.includes('trasera') ||
269
245
  label.includes('posterior');
270
246
  });
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
- });
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
+ }
278
259
  }
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
- });
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];
264
+ }
265
+ this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
285
266
  }
286
- selectAutoCamera() {
267
+ async selectAutoCamera() {
287
268
  if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
288
- this.selectBackCamera();
289
- this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
269
+ await this.selectBackCamera();
290
270
  }
291
271
  else {
292
272
  // For desktop, prefer front camera (usually built-in webcam)
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
- });
273
+ await this.selectFrontCamera();
299
274
  }
300
275
  }
301
276
  updatePreferredFacing(camera) {
@@ -311,40 +286,19 @@ export class CameraService {
311
286
  await this.detectDeviceType();
312
287
  }
313
288
  const videoConstraints = {};
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
289
+ // Priority 1: Always use deviceId if available (ensures specific camera with autofocus)
290
+ if (this.selectedCameraId) {
323
291
  videoConstraints.deviceId = { exact: this.selectedCameraId };
324
- this.logger.state('USANDO_CAMARA_MANUAL', {
325
- deviceId: this.selectedCameraId
326
- });
327
292
  }
293
+ // Priority 2: Use facingMode as fallback when no specific camera is selected
328
294
  else if (this.preferredCameraFacing) {
329
- // Use facingMode for initial preference-based selection
330
- // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
331
295
  const facingConstraint = this.deviceType === 'desktop'
332
296
  ? { ideal: this.preferredCameraFacing }
333
297
  : { exact: this.preferredCameraFacing };
334
298
  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
- });
345
299
  }
300
+ // Priority 3: Fallback to preferredCamera setting
346
301
  else {
347
- // Use preferredCamera setting to determine default facing mode
348
302
  if (this.preferredCamera === 'front') {
349
303
  videoConstraints.facingMode = "user";
350
304
  }
@@ -352,24 +306,25 @@ export class CameraService {
352
306
  videoConstraints.facingMode = "environment";
353
307
  }
354
308
  else {
355
- // Auto mode: use environment for mobile/tablet, user for desktop
356
309
  videoConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
357
310
  }
358
- this.logger.state('USANDO_PREFERENCIA_CONFIGURADA', {
359
- facingMode: videoConstraints.facingMode,
360
- preferredCamera: this.preferredCamera,
361
- deviceType: this.deviceType
362
- });
363
311
  }
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());
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
+ }
370
327
  const constraints = { ...videoConstraints };
371
- // Enhanced focus and image quality settings
372
- this.applyAdvancedCameraSettings(constraints, capabilities);
373
328
  if (capabilities.width && capabilities.height) {
374
329
  const maxWidth = Math.min(capabilities.width.max, 1920);
375
330
  const maxHeight = Math.min(capabilities.height.max, 1080);
@@ -383,15 +338,20 @@ export class CameraService {
383
338
  constraints.height = { ideal: maxHeight };
384
339
  }
385
340
  }
386
- this.logger.state('CONFIGURACION_CAMARA_OPTIMIZADA', {
387
- constraints,
388
- capabilities: this.getCapabilitiesSummary(capabilities)
389
- });
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
+ }
390
351
  return constraints;
391
352
  }
392
353
  catch (err) {
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
354
+ // Fallback logic with minimal temporary streams
395
355
  if (!this.deviceType || this.deviceType === 'desktop') {
396
356
  await this.detectDeviceType();
397
357
  }
@@ -400,25 +360,16 @@ export class CameraService {
400
360
  width: { ideal: isTablet ? 1280 : 1920 },
401
361
  height: { ideal: isTablet ? 720 : 1080 }
402
362
  };
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
363
+ if (this.selectedCameraId) {
407
364
  fallbackConstraints.deviceId = { exact: this.selectedCameraId };
408
365
  }
409
366
  else if (this.preferredCameraFacing) {
410
- // Use facingMode for initial preference-based selection
411
- // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
412
367
  const facingConstraint = this.deviceType === 'desktop'
413
368
  ? { ideal: this.preferredCameraFacing }
414
369
  : { exact: this.preferredCameraFacing };
415
370
  fallbackConstraints.facingMode = facingConstraint;
416
371
  }
417
- else if (this.selectedCameraId) {
418
- fallbackConstraints.deviceId = { exact: this.selectedCameraId };
419
- }
420
372
  else {
421
- // Use preferredCamera setting to determine default facing mode
422
373
  if (this.preferredCamera === 'front') {
423
374
  fallbackConstraints.facingMode = "user";
424
375
  }
@@ -426,165 +377,26 @@ export class CameraService {
426
377
  fallbackConstraints.facingMode = "environment";
427
378
  }
428
379
  else {
429
- // Auto mode: use environment for mobile/tablet, user for desktop
430
380
  fallbackConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
431
381
  }
432
382
  }
433
383
  return fallbackConstraints;
434
384
  }
435
385
  }
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
- }
493
- }
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 };
507
- }
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);
386
+ // Optimization helper methods
387
+ constraintsEqual(a, b) {
388
+ if (!a || !b)
546
389
  return false;
547
- }
390
+ return JSON.stringify(a) === JSON.stringify(b);
548
391
  }
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
- }
392
+ // Cleanup method for optimization
393
+ static clearCaches() {
394
+ CameraService.cameraCapabilitiesCache.clear();
395
+ CameraService.deviceEnumerationCache = null;
396
+ }
397
+ // Method to invalidate device cache when needed
398
+ invalidateDeviceCache() {
399
+ CameraService.deviceEnumerationCache = null;
588
400
  }
589
401
  }
590
402
  //# sourceMappingURL=CameraService.js.map