@jaak.ai/stamps 2.0.1 → 2.1.0-beta.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 (71) hide show
  1. package/README.md +11 -1
  2. package/dist/cjs/app-globals-V2Kpy_OQ.js +8 -0
  3. package/dist/cjs/app-globals-V2Kpy_OQ.js.map +1 -0
  4. package/dist/cjs/{index-BfhtOB0D.js → index-Ga0t6BMe.js} +176 -119
  5. package/dist/cjs/index-Ga0t6BMe.js.map +1 -0
  6. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +5 -4
  7. package/dist/cjs/jaak-stamps-webcomponent.cjs.js.map +1 -1
  8. package/dist/cjs/jaak-stamps.cjs.entry.js +1273 -663
  9. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  10. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  11. package/dist/cjs/loader.cjs.js +4 -3
  12. package/dist/cjs/loader.cjs.js.map +1 -1
  13. package/dist/collection/collection-manifest.json +1 -1
  14. package/dist/collection/components/my-component/my-component.css +225 -14
  15. package/dist/collection/components/my-component/my-component.js +964 -286
  16. package/dist/collection/components/my-component/my-component.js.map +1 -1
  17. package/dist/collection/services/CameraService.js +236 -342
  18. package/dist/collection/services/CameraService.js.map +1 -1
  19. package/dist/collection/services/DetectionService.js +100 -42
  20. package/dist/collection/services/DetectionService.js.map +1 -1
  21. package/dist/collection/services/EventBusService.js +1 -1
  22. package/dist/collection/services/EventBusService.js.map +1 -1
  23. package/dist/collection/services/ServiceContainer.js +4 -13
  24. package/dist/collection/services/ServiceContainer.js.map +1 -1
  25. package/dist/collection/services/interfaces/ICameraService.js.map +1 -1
  26. package/dist/collection/types/component-types.js.map +1 -1
  27. package/dist/components/index.js +165 -113
  28. package/dist/components/index.js.map +1 -1
  29. package/dist/components/jaak-stamps.js +1281 -666
  30. package/dist/components/jaak-stamps.js.map +1 -1
  31. package/dist/esm/app-globals-DQuL1Twl.js +6 -0
  32. package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
  33. package/dist/esm/{index-BP1Q4KOg.js → index-Drbzcuq-.js} +177 -119
  34. package/dist/esm/index-Drbzcuq-.js.map +1 -0
  35. package/dist/esm/jaak-stamps-webcomponent.js +5 -4
  36. package/dist/esm/jaak-stamps-webcomponent.js.map +1 -1
  37. package/dist/esm/jaak-stamps.entry.js +1273 -663
  38. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  39. package/dist/esm/loader.js +4 -3
  40. package/dist/esm/loader.js.map +1 -1
  41. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  42. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js.map +1 -1
  43. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  44. package/dist/jaak-stamps-webcomponent/loader.esm.js.map +1 -1
  45. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js +2 -0
  46. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js.map +1 -0
  47. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js +3 -0
  48. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js.map +1 -0
  49. package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js +2 -0
  50. package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js.map +1 -0
  51. package/dist/types/components/my-component/my-component.d.ts +87 -17
  52. package/dist/types/components.d.ts +14 -8
  53. package/dist/types/services/CameraService.d.ts +13 -14
  54. package/dist/types/services/DetectionService.d.ts +10 -3
  55. package/dist/types/services/ServiceContainer.d.ts +1 -2
  56. package/dist/types/services/interfaces/ICameraService.d.ts +3 -12
  57. package/dist/types/stencil-public-runtime.d.ts +12 -5
  58. package/dist/types/types/component-types.d.ts +3 -0
  59. package/package.json +6 -4
  60. package/dist/cjs/index-BfhtOB0D.js.map +0 -1
  61. package/dist/collection/services/LoggerService.js +0 -40
  62. package/dist/collection/services/LoggerService.js.map +0 -1
  63. package/dist/collection/services/interfaces/ILogger.js +0 -2
  64. package/dist/collection/services/interfaces/ILogger.js.map +0 -1
  65. package/dist/esm/index-BP1Q4KOg.js.map +0 -1
  66. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js +0 -2
  67. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js.map +0 -1
  68. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js +0 -3
  69. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js.map +0 -1
  70. package/dist/types/services/LoggerService.d.ts +0 -12
  71. 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') {
@@ -44,25 +51,31 @@ export class CameraService {
44
51
  tempStream.getTracks().forEach(track => track.stop());
45
52
  }
46
53
  const devices = await navigator.mediaDevices.enumerateDevices();
47
- 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
54
+ const allCameras = devices.filter(device => device.kind === 'videoinput');
55
+ // Filter out virtual cameras
56
+ this.availableCameras = allCameras.filter(camera => {
57
+ const isVirtual = this.isVirtualCamera(camera);
58
+ if (isVirtual) {
59
+ console.log(`Virtual camera detected and filtered: ${camera.label}`);
60
+ }
61
+ return !isVirtual;
60
62
  });
63
+ // Check if there are physical cameras available
64
+ if (this.availableCameras.length === 0) {
65
+ console.log('No physical cameras available');
66
+ this.eventBus.emit('error', new Error('Cámaras virtuales no están permitidas. Use una cámara física.'));
67
+ return [];
68
+ }
69
+ // Cache the results for optimization
70
+ CameraService.deviceEnumerationCache = {
71
+ devices: this.availableCameras,
72
+ timestamp: now
73
+ };
74
+ await this.setInitialCameraPreference();
61
75
  this.eventBus.emit('camera-changed', this.selectedCameraId);
62
76
  return this.availableCameras;
63
77
  }
64
78
  catch (error) {
65
- this.logger.error('Error al enumerar cámaras disponibles:', error);
66
79
  this.handleCameraPermissionError(error);
67
80
  return [];
68
81
  }
@@ -83,8 +96,6 @@ export class CameraService {
83
96
  }
84
97
  this.selectedCameraId = cameraId;
85
98
  this.updatePreferredFacing(camera);
86
- this.isManuallySelected = true; // Mark as manually selected
87
- this.savePreference();
88
99
  this.eventBus.emit('camera-changed', cameraId);
89
100
  }
90
101
  getPreferredFacing() {
@@ -93,14 +104,35 @@ export class CameraService {
93
104
  async setupCamera(constraints) {
94
105
  try {
95
106
  const finalConstraints = constraints || await this.getMaxResolution();
96
- const stream = await navigator.mediaDevices.getUserMedia({
107
+ // Optimization: Check if we can reuse existing stream with same constraints
108
+ if (this.currentStreamPromise && this.constraintsEqual(finalConstraints, this.lastStreamConstraints)) {
109
+ return await this.currentStreamPromise;
110
+ }
111
+ // Create new stream promise for lazy loading
112
+ this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
97
113
  video: finalConstraints,
98
114
  audio: false
99
115
  });
116
+ this.lastStreamConstraints = finalConstraints;
117
+ const stream = await this.currentStreamPromise;
118
+ // Validate that active stream is not from a virtual camera
119
+ const videoTrack = stream.getVideoTracks()[0];
120
+ if (videoTrack) {
121
+ const settings = videoTrack.getSettings();
122
+ const deviceId = settings.deviceId;
123
+ if (deviceId) {
124
+ const devices = await navigator.mediaDevices.enumerateDevices();
125
+ const currentDevice = devices.find(device => device.deviceId === deviceId);
126
+ if (currentDevice && this.isVirtualCamera(currentDevice)) {
127
+ console.log(`Virtual camera detected in active stream: ${currentDevice.label}`);
128
+ stream.getTracks().forEach(track => track.stop());
129
+ throw new Error('Cámaras virtuales no están permitidas. Use una cámara física.');
130
+ }
131
+ }
132
+ }
100
133
  return stream;
101
134
  }
102
135
  catch (error) {
103
- this.logger.error('Error en setupCamera, intentando con restricciones básicas:', error);
104
136
  // Fallback to basic constraints if getMaxResolution fails
105
137
  const basicConstraints = {
106
138
  width: { ideal: 1280 },
@@ -110,65 +142,43 @@ export class CameraService {
110
142
  if (this.deviceType !== 'desktop' && this.preferredCameraFacing) {
111
143
  basicConstraints.facingMode = this.preferredCameraFacing;
112
144
  }
113
- this.logger.state('USANDO_RESTRICCIONES_BASICAS', { constraints: basicConstraints });
114
- return await navigator.mediaDevices.getUserMedia({
145
+ this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
115
146
  video: basicConstraints,
116
147
  audio: false
117
148
  });
149
+ this.lastStreamConstraints = basicConstraints;
150
+ const stream = await this.currentStreamPromise;
151
+ // Validate fallback stream as well
152
+ const videoTrack = stream.getVideoTracks()[0];
153
+ if (videoTrack) {
154
+ const settings = videoTrack.getSettings();
155
+ const deviceId = settings.deviceId;
156
+ if (deviceId) {
157
+ const devices = await navigator.mediaDevices.enumerateDevices();
158
+ const currentDevice = devices.find(device => device.deviceId === deviceId);
159
+ if (currentDevice && this.isVirtualCamera(currentDevice)) {
160
+ console.log(`Virtual camera detected in fallback stream: ${currentDevice.label}`);
161
+ stream.getTracks().forEach(track => track.stop());
162
+ throw new Error('Cámaras virtuales no están permitidas. Use una cámara física.');
163
+ }
164
+ }
165
+ }
166
+ return stream;
118
167
  }
119
168
  }
120
169
  async switchCamera(cameraId) {
121
170
  const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
122
171
  if (!selectedCamera) {
123
- this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
124
172
  await this.enumerateDevices();
125
173
  return;
126
174
  }
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())
175
+ // Validate that the target camera is not virtual
176
+ if (this.isVirtualCamera(selectedCamera)) {
177
+ console.log(`Attempted to switch to virtual camera: ${selectedCamera.label}`);
178
+ this.eventBus.emit('error', new Error('No se puede cambiar a cámara virtual'));
136
179
  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
180
  }
181
+ await this.setSelectedCamera(cameraId);
172
182
  }
173
183
  isRearCamera(stream) {
174
184
  const videoTrack = stream.getVideoTracks()[0];
@@ -177,13 +187,18 @@ export class CameraService {
177
187
  const settings = videoTrack.getSettings();
178
188
  return settings.facingMode === 'environment';
179
189
  }
180
- getCameraInfo() {
181
- return {
182
- availableCameras: this.availableCameras.map(camera => ({
190
+ async getCameraInfo() {
191
+ const availableCamerasWithAutofocus = await Promise.all(this.availableCameras.map(async (camera) => {
192
+ const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
193
+ return {
183
194
  id: camera.deviceId,
184
195
  label: camera.label || 'Unknown Camera',
185
- selected: camera.deviceId === this.selectedCameraId
186
- })),
196
+ selected: camera.deviceId === this.selectedCameraId,
197
+ hasAutofocus
198
+ };
199
+ }));
200
+ return {
201
+ availableCameras: availableCamerasWithAutofocus,
187
202
  selectedCameraId: this.selectedCameraId,
188
203
  deviceType: this.deviceType,
189
204
  isMultipleCamerasAvailable: this.isMultipleCamerasAvailable(),
@@ -199,7 +214,6 @@ export class CameraService {
199
214
  return permission.state;
200
215
  }
201
216
  catch (error) {
202
- this.logger.warn('No se pudo verificar permisos de cámara:', error);
203
217
  return 'prompt';
204
218
  }
205
219
  }
@@ -207,35 +221,48 @@ export class CameraService {
207
221
  this.availableCameras = [];
208
222
  this.eventBus.emit('error', new Error(`Camera permission error: ${error.message}`));
209
223
  }
210
- setInitialCameraPreference() {
224
+ async setInitialCameraPreference() {
211
225
  if (this.availableCameras.length === 0)
212
226
  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
227
  if (this.preferredCamera === 'front') {
221
- this.selectFrontCamera();
228
+ await this.selectFrontCamera();
222
229
  }
223
230
  else if (this.preferredCamera === 'back') {
224
- this.selectBackCamera();
231
+ await this.selectBackCamera();
225
232
  }
226
233
  else {
227
- this.selectAutoCamera();
234
+ await this.selectAutoCamera();
228
235
  }
229
236
  }
230
- selectFrontCamera() {
237
+ async checkCameraAutofocus(deviceId) {
238
+ try {
239
+ // Check cache first for optimization
240
+ if (CameraService.cameraCapabilitiesCache.has(deviceId)) {
241
+ const cached = CameraService.cameraCapabilitiesCache.get(deviceId);
242
+ return typeof cached === 'boolean' ? cached : false;
243
+ }
244
+ const stream = await navigator.mediaDevices.getUserMedia({
245
+ video: { deviceId: { exact: deviceId } }
246
+ });
247
+ const videoTrack = stream.getVideoTracks()[0];
248
+ const capabilities = videoTrack.getCapabilities();
249
+ stream.getTracks().forEach(track => track.stop());
250
+ const hasFocusMode = capabilities.focusMode && Array.isArray(capabilities.focusMode) && capabilities.focusMode.length > 0;
251
+ const hasAutofocus = hasFocusMode && (capabilities.focusMode.includes('continuous') ||
252
+ capabilities.focusMode.includes('auto'));
253
+ // Cache the result for future use
254
+ CameraService.cameraCapabilitiesCache.set(deviceId, hasAutofocus);
255
+ return hasAutofocus;
256
+ }
257
+ catch (error) {
258
+ // Cache negative result to avoid repeated failures
259
+ CameraService.cameraCapabilitiesCache.set(deviceId, false);
260
+ return false;
261
+ }
262
+ }
263
+ async selectFrontCamera() {
231
264
  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 => {
265
+ const frontCameras = this.availableCameras.filter(camera => {
239
266
  const label = camera.label.toLowerCase();
240
267
  return label.includes('front') ||
241
268
  label.includes('user') ||
@@ -243,24 +270,24 @@ export class CameraService {
243
270
  label.includes('frontal') ||
244
271
  (!label.includes('back') && !label.includes('rear') && !label.includes('environment'));
245
272
  });
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
- });
273
+ let selectedCamera = null;
274
+ if (frontCameras.length > 0) {
275
+ for (const camera of frontCameras) {
276
+ const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
277
+ if (hasAutofocus) {
278
+ selectedCamera = camera;
279
+ break;
280
+ }
281
+ }
282
+ if (!selectedCamera) {
283
+ selectedCamera = frontCameras[0];
284
+ }
285
+ }
286
+ this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
253
287
  }
254
- selectBackCamera() {
288
+ async selectBackCamera() {
255
289
  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 => {
290
+ const backCameras = this.availableCameras.filter(camera => {
264
291
  const label = camera.label.toLowerCase();
265
292
  return label.includes('back') ||
266
293
  label.includes('rear') ||
@@ -268,34 +295,33 @@ export class CameraService {
268
295
  label.includes('trasera') ||
269
296
  label.includes('posterior');
270
297
  });
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
- });
298
+ let selectedCamera = null;
299
+ if (backCameras.length > 0) {
300
+ for (const camera of backCameras) {
301
+ const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
302
+ if (hasAutofocus) {
303
+ selectedCamera = camera;
304
+ break;
305
+ }
306
+ }
307
+ if (!selectedCamera) {
308
+ selectedCamera = backCameras[0];
309
+ }
278
310
  }
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
- });
311
+ else if (this.availableCameras.length > 1) {
312
+ const lastCamera = this.availableCameras[this.availableCameras.length - 1];
313
+ const hasAutofocus = await this.checkCameraAutofocus(lastCamera.deviceId);
314
+ selectedCamera = hasAutofocus ? lastCamera : this.availableCameras[this.availableCameras.length - 1];
315
+ }
316
+ this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
285
317
  }
286
- selectAutoCamera() {
318
+ async selectAutoCamera() {
287
319
  if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
288
- this.selectBackCamera();
289
- this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
320
+ await this.selectBackCamera();
290
321
  }
291
322
  else {
292
323
  // 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
- });
324
+ await this.selectFrontCamera();
299
325
  }
300
326
  }
301
327
  updatePreferredFacing(camera) {
@@ -311,40 +337,19 @@ export class CameraService {
311
337
  await this.detectDeviceType();
312
338
  }
313
339
  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
340
+ // Priority 1: Always use deviceId if available (ensures specific camera with autofocus)
341
+ if (this.selectedCameraId) {
323
342
  videoConstraints.deviceId = { exact: this.selectedCameraId };
324
- this.logger.state('USANDO_CAMARA_MANUAL', {
325
- deviceId: this.selectedCameraId
326
- });
327
343
  }
344
+ // Priority 2: Use facingMode as fallback when no specific camera is selected
328
345
  else if (this.preferredCameraFacing) {
329
- // Use facingMode for initial preference-based selection
330
- // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
331
346
  const facingConstraint = this.deviceType === 'desktop'
332
347
  ? { ideal: this.preferredCameraFacing }
333
348
  : { exact: this.preferredCameraFacing };
334
349
  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
350
  }
351
+ // Priority 3: Fallback to preferredCamera setting
346
352
  else {
347
- // Use preferredCamera setting to determine default facing mode
348
353
  if (this.preferredCamera === 'front') {
349
354
  videoConstraints.facingMode = "user";
350
355
  }
@@ -352,24 +357,25 @@ export class CameraService {
352
357
  videoConstraints.facingMode = "environment";
353
358
  }
354
359
  else {
355
- // Auto mode: use environment for mobile/tablet, user for desktop
356
360
  videoConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
357
361
  }
358
- this.logger.state('USANDO_PREFERENCIA_CONFIGURADA', {
359
- facingMode: videoConstraints.facingMode,
360
- preferredCamera: this.preferredCamera,
361
- deviceType: this.deviceType
362
- });
363
362
  }
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());
363
+ // Optimization: Use cached capabilities if available
364
+ let capabilities;
365
+ if (this.selectedCameraId && CameraService.cameraCapabilitiesCache.has(this.selectedCameraId + '_caps')) {
366
+ capabilities = JSON.parse(CameraService.cameraCapabilitiesCache.get(this.selectedCameraId + '_caps'));
367
+ }
368
+ else {
369
+ const tempStream = await navigator.mediaDevices.getUserMedia({ video: videoConstraints });
370
+ const videoTrack = tempStream.getVideoTracks()[0];
371
+ capabilities = videoTrack.getCapabilities();
372
+ tempStream.getTracks().forEach(track => track.stop());
373
+ // Cache capabilities for future use
374
+ if (this.selectedCameraId) {
375
+ CameraService.cameraCapabilitiesCache.set(this.selectedCameraId + '_caps', JSON.stringify(capabilities));
376
+ }
377
+ }
370
378
  const constraints = { ...videoConstraints };
371
- // Enhanced focus and image quality settings
372
- this.applyAdvancedCameraSettings(constraints, capabilities);
373
379
  if (capabilities.width && capabilities.height) {
374
380
  const maxWidth = Math.min(capabilities.width.max, 1920);
375
381
  const maxHeight = Math.min(capabilities.height.max, 1080);
@@ -383,15 +389,20 @@ export class CameraService {
383
389
  constraints.height = { ideal: maxHeight };
384
390
  }
385
391
  }
386
- this.logger.state('CONFIGURACION_CAMARA_OPTIMIZADA', {
387
- constraints,
388
- capabilities: this.getCapabilitiesSummary(capabilities)
389
- });
392
+ // Configure autofocus if camera has the capability
393
+ const capabilitiesAny = capabilities;
394
+ if (capabilitiesAny.focusMode && Array.isArray(capabilitiesAny.focusMode)) {
395
+ if (capabilitiesAny.focusMode.includes('continuous')) {
396
+ constraints.focusMode = 'continuous';
397
+ }
398
+ else if (capabilitiesAny.focusMode.includes('auto')) {
399
+ constraints.focusMode = 'auto';
400
+ }
401
+ }
390
402
  return constraints;
391
403
  }
392
404
  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
405
+ // Fallback logic with minimal temporary streams
395
406
  if (!this.deviceType || this.deviceType === 'desktop') {
396
407
  await this.detectDeviceType();
397
408
  }
@@ -400,25 +411,16 @@ export class CameraService {
400
411
  width: { ideal: isTablet ? 1280 : 1920 },
401
412
  height: { ideal: isTablet ? 720 : 1080 }
402
413
  };
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
414
+ if (this.selectedCameraId) {
407
415
  fallbackConstraints.deviceId = { exact: this.selectedCameraId };
408
416
  }
409
417
  else if (this.preferredCameraFacing) {
410
- // Use facingMode for initial preference-based selection
411
- // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
412
418
  const facingConstraint = this.deviceType === 'desktop'
413
419
  ? { ideal: this.preferredCameraFacing }
414
420
  : { exact: this.preferredCameraFacing };
415
421
  fallbackConstraints.facingMode = facingConstraint;
416
422
  }
417
- else if (this.selectedCameraId) {
418
- fallbackConstraints.deviceId = { exact: this.selectedCameraId };
419
- }
420
423
  else {
421
- // Use preferredCamera setting to determine default facing mode
422
424
  if (this.preferredCamera === 'front') {
423
425
  fallbackConstraints.facingMode = "user";
424
426
  }
@@ -426,165 +428,57 @@ export class CameraService {
426
428
  fallbackConstraints.facingMode = "environment";
427
429
  }
428
430
  else {
429
- // Auto mode: use environment for mobile/tablet, user for desktop
430
431
  fallbackConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
431
432
  }
432
433
  }
433
434
  return fallbackConstraints;
434
435
  }
435
436
  }
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 };
437
+ // Optimization helper methods
438
+ constraintsEqual(a, b) {
439
+ if (!a || !b)
440
+ return false;
441
+ return JSON.stringify(a) === JSON.stringify(b);
507
442
  }
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
- };
443
+ // Cleanup method for optimization
444
+ static clearCaches() {
445
+ CameraService.cameraCapabilitiesCache.clear();
446
+ CameraService.deviceEnumerationCache = null;
520
447
  }
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
- }
448
+ // Method to invalidate device cache when needed
449
+ invalidateDeviceCache() {
450
+ CameraService.deviceEnumerationCache = null;
548
451
  }
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
- }
452
+ isVirtualCamera(device) {
453
+ const label = device.label.toLowerCase();
454
+ const virtualCameraIndicators = [
455
+ 'obs',
456
+ 'virtual',
457
+ 'snap camera',
458
+ 'manycam',
459
+ 'xsplit',
460
+ 'camtwist',
461
+ 'ecamm',
462
+ 'nvidia broadcast',
463
+ 'droidcam',
464
+ 'iriun',
465
+ 'elgato',
466
+ 'streamlabs',
467
+ 'wirecast',
468
+ 'zoom virtual',
469
+ 'teams virtual',
470
+ 'mmhmm',
471
+ 'camo',
472
+ 'reincubate camo',
473
+ 'webcamoid',
474
+ 'splitcam',
475
+ 'cheese',
476
+ 'guvcview',
477
+ 'yawcam',
478
+ 'webcam 7',
479
+ 'altserver'
480
+ ];
481
+ return virtualCameraIndicators.some(indicator => label.includes(indicator));
588
482
  }
589
483
  }
590
484
  //# sourceMappingURL=CameraService.js.map