@jaak.ai/stamps 2.0.0-beta.2 → 2.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -212
- package/dist/cjs/{index-DGM9-FNg.js → index-BfhtOB0D.js} +5 -2
- package/dist/cjs/index-BfhtOB0D.js.map +1 -0
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +2 -2
- package/dist/cjs/jaak-stamps.cjs.entry.js +1860 -783
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/collection/components/my-component/my-component.css +629 -72
- package/dist/collection/components/my-component/my-component.js +896 -781
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/CameraService.js +453 -0
- package/dist/collection/services/CameraService.js.map +1 -0
- package/dist/collection/services/DetectionService.js +369 -0
- package/dist/collection/services/DetectionService.js.map +1 -0
- package/dist/collection/services/EventBusService.js +42 -0
- package/dist/collection/services/EventBusService.js.map +1 -0
- package/dist/collection/services/LoggerService.js +40 -0
- package/dist/collection/services/LoggerService.js.map +1 -0
- package/dist/collection/services/ServiceContainer.js +66 -0
- package/dist/collection/services/ServiceContainer.js.map +1 -0
- package/dist/collection/services/StateManagerService.js +109 -0
- package/dist/collection/services/StateManagerService.js.map +1 -0
- package/dist/collection/services/factories/DeviceStrategyFactory.js +16 -0
- package/dist/collection/services/factories/DeviceStrategyFactory.js.map +1 -0
- package/dist/collection/services/interfaces/ICameraService.js +2 -0
- package/dist/collection/services/interfaces/ICameraService.js.map +1 -0
- package/dist/collection/services/interfaces/IDetectionService.js +2 -0
- package/dist/collection/services/interfaces/IDetectionService.js.map +1 -0
- package/dist/collection/services/interfaces/IEventBus.js +2 -0
- package/dist/collection/services/interfaces/IEventBus.js.map +1 -0
- package/dist/collection/services/interfaces/ILogger.js +2 -0
- package/dist/collection/services/interfaces/ILogger.js.map +1 -0
- package/dist/collection/services/interfaces/IStateManager.js +2 -0
- package/dist/collection/services/interfaces/IStateManager.js.map +1 -0
- package/dist/collection/services/strategies/HighPerformanceDeviceStrategy.js +30 -0
- package/dist/collection/services/strategies/HighPerformanceDeviceStrategy.js.map +1 -0
- package/dist/collection/services/strategies/IDeviceStrategy.js +2 -0
- package/dist/collection/services/strategies/IDeviceStrategy.js.map +1 -0
- package/dist/collection/services/strategies/LowMemoryDeviceStrategy.js +30 -0
- package/dist/collection/services/strategies/LowMemoryDeviceStrategy.js.map +1 -0
- package/dist/collection/types/component-types.js +2 -0
- package/dist/collection/types/component-types.js.map +1 -0
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/jaak-stamps.js +1873 -797
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/{index-DqoVMnc7.js → index-BP1Q4KOg.js} +5 -2
- package/dist/esm/index-BP1Q4KOg.js.map +1 -0
- package/dist/esm/jaak-stamps-webcomponent.js +3 -3
- package/dist/esm/jaak-stamps.entry.js +1860 -783
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +3 -3
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-10ee2dab.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-10ee2dab.entry.js.map +1 -0
- package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js +3 -0
- package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +90 -79
- package/dist/types/components.d.ts +32 -8
- package/dist/types/services/CameraService.d.ts +46 -0
- package/dist/types/services/DetectionService.d.ts +35 -0
- package/dist/types/services/EventBusService.d.ts +9 -0
- package/dist/types/services/LoggerService.d.ts +12 -0
- package/dist/types/services/ServiceContainer.d.ts +27 -0
- package/dist/types/services/StateManagerService.d.ts +15 -0
- package/dist/types/services/factories/DeviceStrategyFactory.d.ts +4 -0
- package/dist/types/services/interfaces/ICameraService.d.ts +34 -0
- package/dist/types/services/interfaces/IDetectionService.d.ts +31 -0
- package/dist/types/services/interfaces/IEventBus.d.ts +16 -0
- package/dist/types/services/interfaces/ILogger.d.ts +8 -0
- package/dist/types/services/interfaces/IStateManager.d.ts +35 -0
- package/dist/types/services/strategies/HighPerformanceDeviceStrategy.d.ts +6 -0
- package/dist/types/services/strategies/IDeviceStrategy.d.ts +21 -0
- package/dist/types/services/strategies/LowMemoryDeviceStrategy.d.ts +6 -0
- package/dist/types/types/component-types.d.ts +36 -0
- package/package.json +3 -3
- package/dist/cjs/index-DGM9-FNg.js.map +0 -1
- package/dist/esm/index-DqoVMnc7.js.map +0 -1
- package/dist/jaak-stamps-webcomponent/p-DqoVMnc7.js +0 -3
- package/dist/jaak-stamps-webcomponent/p-DqoVMnc7.js.map +0 -1
- package/dist/jaak-stamps-webcomponent/p-f1172b06.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-f1172b06.entry.js.map +0 -1
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
export class CameraService {
|
|
2
|
+
logger;
|
|
3
|
+
eventBus;
|
|
4
|
+
availableCameras = [];
|
|
5
|
+
selectedCameraId = null;
|
|
6
|
+
deviceType = 'desktop';
|
|
7
|
+
preferredCameraFacing = null;
|
|
8
|
+
preferredCamera = 'auto';
|
|
9
|
+
constructor(logger, eventBus, preferredCamera = 'auto') {
|
|
10
|
+
this.logger = logger;
|
|
11
|
+
this.eventBus = eventBus;
|
|
12
|
+
this.preferredCamera = preferredCamera;
|
|
13
|
+
}
|
|
14
|
+
async detectDeviceType() {
|
|
15
|
+
const userAgent = navigator.userAgent;
|
|
16
|
+
const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
|
17
|
+
const isTablet = /iPad|Android/i.test(userAgent) && window.innerWidth >= 768;
|
|
18
|
+
if (isTablet) {
|
|
19
|
+
this.deviceType = 'tablet';
|
|
20
|
+
}
|
|
21
|
+
else if (isMobile) {
|
|
22
|
+
this.deviceType = 'mobile';
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
this.deviceType = 'desktop';
|
|
26
|
+
}
|
|
27
|
+
this.logger.state('DISPOSITIVO_DETECTADO', {
|
|
28
|
+
deviceType: this.deviceType,
|
|
29
|
+
userAgent: navigator.userAgent,
|
|
30
|
+
screenDimensions: { width: window.innerWidth, height: window.innerHeight }
|
|
31
|
+
});
|
|
32
|
+
return this.deviceType;
|
|
33
|
+
}
|
|
34
|
+
async enumerateDevices() {
|
|
35
|
+
try {
|
|
36
|
+
const permissionStatus = await this.checkCameraPermission();
|
|
37
|
+
if (permissionStatus === 'denied') {
|
|
38
|
+
this.logger.error('Permiso de cámara denegado por el usuario');
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
if (permissionStatus === 'prompt') {
|
|
42
|
+
const tempStream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
43
|
+
tempStream.getTracks().forEach(track => track.stop());
|
|
44
|
+
}
|
|
45
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
46
|
+
this.availableCameras = devices.filter(device => device.kind === 'videoinput');
|
|
47
|
+
this.logger.state('CAMARAS_DETECTADAS', {
|
|
48
|
+
count: this.availableCameras.length,
|
|
49
|
+
cameras: this.availableCameras.map(cam => ({
|
|
50
|
+
id: cam.deviceId,
|
|
51
|
+
label: cam.label || 'Unknown Camera'
|
|
52
|
+
}))
|
|
53
|
+
});
|
|
54
|
+
this.setInitialCameraPreference();
|
|
55
|
+
this.eventBus.emit('camera-changed', this.selectedCameraId);
|
|
56
|
+
return this.availableCameras;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
this.logger.error('Error al enumerar cámaras disponibles:', error);
|
|
60
|
+
this.handleCameraPermissionError(error);
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
getAvailableCameras() {
|
|
65
|
+
return [...this.availableCameras];
|
|
66
|
+
}
|
|
67
|
+
isMultipleCamerasAvailable() {
|
|
68
|
+
return this.availableCameras.length > 1;
|
|
69
|
+
}
|
|
70
|
+
getSelectedCameraId() {
|
|
71
|
+
return this.selectedCameraId;
|
|
72
|
+
}
|
|
73
|
+
async setSelectedCamera(cameraId) {
|
|
74
|
+
const camera = this.availableCameras.find(cam => cam.deviceId === cameraId);
|
|
75
|
+
if (!camera) {
|
|
76
|
+
throw new Error(`Camera with ID ${cameraId} not found`);
|
|
77
|
+
}
|
|
78
|
+
this.selectedCameraId = cameraId;
|
|
79
|
+
this.updatePreferredFacing(camera);
|
|
80
|
+
this.savePreference();
|
|
81
|
+
this.eventBus.emit('camera-changed', cameraId);
|
|
82
|
+
}
|
|
83
|
+
getPreferredFacing() {
|
|
84
|
+
return this.preferredCameraFacing;
|
|
85
|
+
}
|
|
86
|
+
async setupCamera(constraints) {
|
|
87
|
+
const finalConstraints = constraints || await this.getMaxResolution();
|
|
88
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
89
|
+
video: finalConstraints,
|
|
90
|
+
audio: false
|
|
91
|
+
});
|
|
92
|
+
return stream;
|
|
93
|
+
}
|
|
94
|
+
async switchCamera(cameraId) {
|
|
95
|
+
const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
|
|
96
|
+
if (!selectedCamera) {
|
|
97
|
+
this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
|
|
98
|
+
await this.enumerateDevices();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await this.setSelectedCamera(cameraId);
|
|
102
|
+
this.logger.state('CAMARA_CAMBIADA', {
|
|
103
|
+
label: selectedCamera.label,
|
|
104
|
+
deviceId: selectedCamera.deviceId
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async flipToNextCamera() {
|
|
108
|
+
if (!this.isMultipleCamerasAvailable())
|
|
109
|
+
return;
|
|
110
|
+
const currentIndex = this.availableCameras.findIndex(camera => camera.deviceId === this.selectedCameraId);
|
|
111
|
+
const nextIndex = (currentIndex + 1) % this.availableCameras.length;
|
|
112
|
+
const nextCamera = this.availableCameras[nextIndex];
|
|
113
|
+
await this.switchCamera(nextCamera.deviceId);
|
|
114
|
+
}
|
|
115
|
+
savePreference() {
|
|
116
|
+
try {
|
|
117
|
+
const preference = {
|
|
118
|
+
cameraId: this.selectedCameraId,
|
|
119
|
+
facing: this.preferredCameraFacing,
|
|
120
|
+
timestamp: Date.now()
|
|
121
|
+
};
|
|
122
|
+
localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
|
|
123
|
+
this.logger.state('PREFERENCIA_CAMARA_GUARDADA', preference);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
this.logger.warn('Error al guardar preferencia de cámara:', error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
loadPreference() {
|
|
130
|
+
try {
|
|
131
|
+
const saved = localStorage.getItem('jaak-stamps-camera-preference');
|
|
132
|
+
if (saved) {
|
|
133
|
+
const preference = JSON.parse(saved);
|
|
134
|
+
const isStillAvailable = this.availableCameras.some(camera => camera.deviceId === preference.cameraId);
|
|
135
|
+
if (isStillAvailable) {
|
|
136
|
+
this.selectedCameraId = preference.cameraId;
|
|
137
|
+
this.preferredCameraFacing = preference.facing;
|
|
138
|
+
this.logger.state('PREFERENCIA_CAMARA_CARGADA', preference);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
this.logger.warn('Error al cargar preferencia de cámara:', error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
isRearCamera(stream) {
|
|
147
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
148
|
+
if (!videoTrack)
|
|
149
|
+
return false;
|
|
150
|
+
const settings = videoTrack.getSettings();
|
|
151
|
+
return settings.facingMode === 'environment';
|
|
152
|
+
}
|
|
153
|
+
getCameraInfo() {
|
|
154
|
+
return {
|
|
155
|
+
availableCameras: this.availableCameras.map(camera => ({
|
|
156
|
+
id: camera.deviceId,
|
|
157
|
+
label: camera.label || 'Unknown Camera',
|
|
158
|
+
selected: camera.deviceId === this.selectedCameraId
|
|
159
|
+
})),
|
|
160
|
+
selectedCameraId: this.selectedCameraId,
|
|
161
|
+
deviceType: this.deviceType,
|
|
162
|
+
isMultipleCamerasAvailable: this.isMultipleCamerasAvailable(),
|
|
163
|
+
preferredFacing: this.preferredCameraFacing
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
async checkCameraPermission() {
|
|
167
|
+
try {
|
|
168
|
+
if (!navigator.permissions) {
|
|
169
|
+
return 'prompt';
|
|
170
|
+
}
|
|
171
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
172
|
+
return permission.state;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
this.logger.warn('No se pudo verificar permisos de cámara:', error);
|
|
176
|
+
return 'prompt';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
handleCameraPermissionError(error) {
|
|
180
|
+
this.availableCameras = [];
|
|
181
|
+
this.eventBus.emit('error', new Error(`Camera permission error: ${error.message}`));
|
|
182
|
+
}
|
|
183
|
+
setInitialCameraPreference() {
|
|
184
|
+
if (this.availableCameras.length === 0)
|
|
185
|
+
return;
|
|
186
|
+
if (this.preferredCamera === 'front') {
|
|
187
|
+
this.selectFrontCamera();
|
|
188
|
+
}
|
|
189
|
+
else if (this.preferredCamera === 'back') {
|
|
190
|
+
this.selectBackCamera();
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.selectAutoCamera();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
selectFrontCamera() {
|
|
197
|
+
this.preferredCameraFacing = 'user';
|
|
198
|
+
const frontCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('front') ||
|
|
199
|
+
camera.label.toLowerCase().includes('user') ||
|
|
200
|
+
camera.label.toLowerCase().includes('selfie') ||
|
|
201
|
+
!camera.label.toLowerCase().includes('back') && !camera.label.toLowerCase().includes('rear'));
|
|
202
|
+
this.selectedCameraId = frontCamera ? frontCamera.deviceId : this.availableCameras[0].deviceId;
|
|
203
|
+
this.logger.state('CAMARA_FRONTAL_SELECCIONADA', {
|
|
204
|
+
label: frontCamera?.label || this.availableCameras[0].label,
|
|
205
|
+
deviceId: this.selectedCameraId
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
selectBackCamera() {
|
|
209
|
+
this.preferredCameraFacing = 'environment';
|
|
210
|
+
const backCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('back') ||
|
|
211
|
+
camera.label.toLowerCase().includes('rear') ||
|
|
212
|
+
camera.label.toLowerCase().includes('environment'));
|
|
213
|
+
this.selectedCameraId = backCamera ? backCamera.deviceId : this.availableCameras[0].deviceId;
|
|
214
|
+
this.logger.state('CAMARA_TRASERA_SELECCIONADA', {
|
|
215
|
+
label: backCamera?.label || this.availableCameras[0].label,
|
|
216
|
+
deviceId: this.selectedCameraId
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
selectAutoCamera() {
|
|
220
|
+
if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
|
|
221
|
+
this.selectBackCamera();
|
|
222
|
+
this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
this.selectedCameraId = this.availableCameras[0].deviceId;
|
|
226
|
+
this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', {
|
|
227
|
+
label: this.availableCameras[0].label,
|
|
228
|
+
deviceId: this.selectedCameraId
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
updatePreferredFacing(camera) {
|
|
233
|
+
const isRearCamera = camera.label.toLowerCase().includes('back') ||
|
|
234
|
+
camera.label.toLowerCase().includes('rear') ||
|
|
235
|
+
camera.label.toLowerCase().includes('environment');
|
|
236
|
+
this.preferredCameraFacing = isRearCamera ? 'environment' : 'user';
|
|
237
|
+
}
|
|
238
|
+
async getMaxResolution() {
|
|
239
|
+
try {
|
|
240
|
+
const videoConstraints = {};
|
|
241
|
+
if (this.selectedCameraId) {
|
|
242
|
+
videoConstraints.deviceId = { exact: this.selectedCameraId };
|
|
243
|
+
}
|
|
244
|
+
else if (this.preferredCameraFacing) {
|
|
245
|
+
videoConstraints.facingMode = this.preferredCameraFacing;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
videoConstraints.facingMode = "environment";
|
|
249
|
+
}
|
|
250
|
+
const tempStream = await navigator.mediaDevices.getUserMedia({
|
|
251
|
+
video: videoConstraints
|
|
252
|
+
});
|
|
253
|
+
const videoTrack = tempStream.getVideoTracks()[0];
|
|
254
|
+
const capabilities = videoTrack.getCapabilities();
|
|
255
|
+
tempStream.getTracks().forEach(track => track.stop());
|
|
256
|
+
const constraints = { ...videoConstraints };
|
|
257
|
+
// Enhanced focus and image quality settings
|
|
258
|
+
this.applyAdvancedCameraSettings(constraints, capabilities);
|
|
259
|
+
if (capabilities.width && capabilities.height) {
|
|
260
|
+
const maxWidth = Math.min(capabilities.width.max, 1920);
|
|
261
|
+
const maxHeight = Math.min(capabilities.height.max, 1080);
|
|
262
|
+
const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
|
|
263
|
+
if (isTablet) {
|
|
264
|
+
constraints.width = { ideal: Math.min(maxWidth, 1280) };
|
|
265
|
+
constraints.height = { ideal: Math.min(maxHeight, 720) };
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
constraints.width = { ideal: maxWidth };
|
|
269
|
+
constraints.height = { ideal: maxHeight };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
this.logger.state('CONFIGURACION_CAMARA_OPTIMIZADA', {
|
|
273
|
+
constraints,
|
|
274
|
+
capabilities: this.getCapabilitiesSummary(capabilities)
|
|
275
|
+
});
|
|
276
|
+
return constraints;
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
this.logger.warn('No se pudieron obtener capacidades de cámara, usando configuración de respaldo');
|
|
280
|
+
const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
|
|
281
|
+
const fallbackConstraints = {
|
|
282
|
+
width: { ideal: isTablet ? 1280 : 1920 },
|
|
283
|
+
height: { ideal: isTablet ? 720 : 1080 }
|
|
284
|
+
};
|
|
285
|
+
// Apply basic focus settings even for fallback
|
|
286
|
+
this.applyBasicFocusSettings(fallbackConstraints);
|
|
287
|
+
if (this.selectedCameraId) {
|
|
288
|
+
fallbackConstraints.deviceId = { exact: this.selectedCameraId };
|
|
289
|
+
}
|
|
290
|
+
else if (this.preferredCameraFacing) {
|
|
291
|
+
fallbackConstraints.facingMode = this.preferredCameraFacing;
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
fallbackConstraints.facingMode = "environment";
|
|
295
|
+
}
|
|
296
|
+
return fallbackConstraints;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
applyAdvancedCameraSettings(constraints, capabilities) {
|
|
300
|
+
// Apply standard camera settings that are widely supported
|
|
301
|
+
// Frame rate optimization
|
|
302
|
+
if (capabilities.frameRate) {
|
|
303
|
+
constraints.frameRate = {
|
|
304
|
+
ideal: 30,
|
|
305
|
+
min: 15,
|
|
306
|
+
max: 60
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// Width and height constraints for optimal resolution
|
|
310
|
+
if (capabilities.width && capabilities.height) {
|
|
311
|
+
constraints.width = {
|
|
312
|
+
ideal: Math.min(1920, capabilities.width.max || 1920),
|
|
313
|
+
min: 640
|
|
314
|
+
};
|
|
315
|
+
constraints.height = {
|
|
316
|
+
ideal: Math.min(1080, capabilities.height.max || 1080),
|
|
317
|
+
min: 480
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// Apply enhanced constraints through advanced array for better browser compatibility
|
|
321
|
+
const advancedConstraints = [];
|
|
322
|
+
// Try to set optimal settings through advanced constraints
|
|
323
|
+
const advanced = {};
|
|
324
|
+
// Focus mode - use 'any' type to bypass TypeScript limitations
|
|
325
|
+
if (capabilities.focusMode) {
|
|
326
|
+
if (capabilities.focusMode.includes('continuous')) {
|
|
327
|
+
advanced.focusMode = 'continuous';
|
|
328
|
+
}
|
|
329
|
+
else if (capabilities.focusMode.includes('single-shot')) {
|
|
330
|
+
advanced.focusMode = 'single-shot';
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Focus distance for close objects
|
|
334
|
+
if (capabilities.focusDistance) {
|
|
335
|
+
advanced.focusDistance = {
|
|
336
|
+
ideal: 0.4, // 40cm - optimal for document capture
|
|
337
|
+
min: 0.2,
|
|
338
|
+
max: 1.0
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
// Zoom control
|
|
342
|
+
if (capabilities.zoom) {
|
|
343
|
+
advanced.zoom = {
|
|
344
|
+
ideal: 1.0,
|
|
345
|
+
min: capabilities.zoom.min,
|
|
346
|
+
max: Math.min(capabilities.zoom.max, 3.0)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// Add advanced constraints if any were set
|
|
350
|
+
if (Object.keys(advanced).length > 0) {
|
|
351
|
+
advancedConstraints.push(advanced);
|
|
352
|
+
}
|
|
353
|
+
if (advancedConstraints.length > 0) {
|
|
354
|
+
constraints.advanced = advancedConstraints;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
applyBasicFocusSettings(constraints) {
|
|
358
|
+
// Apply basic focus settings when capabilities are not available
|
|
359
|
+
// Use advanced constraints for non-standard properties
|
|
360
|
+
const advanced = {
|
|
361
|
+
focusMode: 'continuous',
|
|
362
|
+
exposureMode: 'continuous',
|
|
363
|
+
whiteBalanceMode: 'continuous'
|
|
364
|
+
};
|
|
365
|
+
if (!constraints.advanced) {
|
|
366
|
+
constraints.advanced = [];
|
|
367
|
+
}
|
|
368
|
+
constraints.advanced.push(advanced);
|
|
369
|
+
constraints.frameRate = { ideal: 30 };
|
|
370
|
+
}
|
|
371
|
+
getCapabilitiesSummary(capabilities) {
|
|
372
|
+
return {
|
|
373
|
+
hasAutoFocus: !!capabilities.focusMode,
|
|
374
|
+
hasFocusDistance: !!capabilities.focusDistance,
|
|
375
|
+
hasExposureControl: !!capabilities.exposureMode,
|
|
376
|
+
hasWhiteBalance: !!capabilities.whiteBalanceMode,
|
|
377
|
+
hasZoom: !!capabilities.zoom,
|
|
378
|
+
hasTorch: capabilities.torch !== undefined,
|
|
379
|
+
hasImageControls: !!(capabilities.brightness || capabilities.contrast || capabilities.saturation || capabilities.sharpness),
|
|
380
|
+
resolutionRange: capabilities.width && capabilities.height ?
|
|
381
|
+
`${capabilities.width.min}x${capabilities.height.min} - ${capabilities.width.max}x${capabilities.height.max}` : 'unknown'
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
// New method to enable torch/flash for better illumination
|
|
385
|
+
async setTorchEnabled(enabled, stream) {
|
|
386
|
+
try {
|
|
387
|
+
if (!stream) {
|
|
388
|
+
this.logger.warn('No hay stream disponible para controlar el flash');
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
392
|
+
if (!videoTrack) {
|
|
393
|
+
this.logger.warn('No hay track de video disponible');
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
const capabilities = videoTrack.getCapabilities();
|
|
397
|
+
if (capabilities.torch === undefined) {
|
|
398
|
+
this.logger.warn('El dispositivo no soporta control de flash');
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
await videoTrack.applyConstraints({
|
|
402
|
+
advanced: [{ torch: enabled }]
|
|
403
|
+
});
|
|
404
|
+
this.logger.state('FLASH_CONTROLADO', { enabled });
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
this.logger.error('Error al controlar el flash:', error);
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Method to manually trigger focus on a specific point
|
|
413
|
+
async focusAtPoint(x, y, stream) {
|
|
414
|
+
try {
|
|
415
|
+
if (!stream) {
|
|
416
|
+
this.logger.warn('No hay stream disponible para enfocar');
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
420
|
+
if (!videoTrack) {
|
|
421
|
+
this.logger.warn('No hay track de video disponible');
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
const capabilities = videoTrack.getCapabilities();
|
|
425
|
+
if (!capabilities.focusMode || !capabilities.focusMode.includes('single-shot')) {
|
|
426
|
+
this.logger.warn('El dispositivo no soporta enfoque manual');
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
// Trigger single-shot focus
|
|
430
|
+
await videoTrack.applyConstraints({
|
|
431
|
+
advanced: [{ focusMode: 'single-shot' }]
|
|
432
|
+
});
|
|
433
|
+
// Return to continuous focus after a short delay
|
|
434
|
+
setTimeout(async () => {
|
|
435
|
+
try {
|
|
436
|
+
await videoTrack.applyConstraints({
|
|
437
|
+
advanced: [{ focusMode: 'continuous' }]
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
this.logger.warn('Error al volver a enfoque continuo:', error);
|
|
442
|
+
}
|
|
443
|
+
}, 1000);
|
|
444
|
+
this.logger.state('ENFOQUE_MANUAL_ACTIVADO', { x, y });
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
this.logger.error('Error al enfocar manualmente:', error);
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
//# sourceMappingURL=CameraService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CameraService.js","sourceRoot":"","sources":["../../src/services/CameraService.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,aAAa;IAQd;IACA;IARF,gBAAgB,GAAsB,EAAE,CAAC;IACzC,gBAAgB,GAAkB,IAAI,CAAC;IACvC,UAAU,GAAoC,SAAS,CAAC;IACxD,qBAAqB,GAAkC,IAAI,CAAC;IAC5D,eAAe,GAA8B,MAAM,CAAC;IAE5D,YACU,MAAe,EACf,QAAmB,EAC3B,kBAA6C,MAAM;QAF3C,WAAM,GAAN,MAAM,CAAS;QACf,aAAQ,GAAR,QAAQ,CAAW;QAG3B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;QACtC,MAAM,QAAQ,GAAG,0DAA0D,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5F,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,UAAU,IAAI,GAAG,CAAC;QAE7E,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC;QAC7B,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACpB,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC9B,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,EAAE;YACzC,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,SAAS,EAAE,SAAS,CAAC,SAAS;YAC9B,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;SAC3E,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,IAAI,CAAC;YACH,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAE5D,IAAI,gBAAgB,KAAK,QAAQ,EAAE,CAAC;gBAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;gBAC/D,OAAO,EAAE,CAAC;YACZ,CAAC;YAED,IAAI,gBAAgB,KAAK,QAAQ,EAAE,CAAC;gBAClC,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC9E,UAAU,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACxD,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,gBAAgB,EAAE,CAAC;YAChE,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;YAE/E,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE;gBACtC,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAAC,MAAM;gBACnC,OAAO,EAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;oBACzC,EAAE,EAAE,GAAG,CAAC,QAAQ;oBAChB,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,gBAAgB;iBACrC,CAAC,CAAC;aACJ,CAAC,CAAC;YAEH,IAAI,CAAC,0BAA0B,EAAE,CAAC;YAClC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAE5D,OAAO,IAAI,CAAC,gBAAgB,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;YACnE,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,CAAC;YACxC,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,mBAAmB;QACjB,OAAO,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;IAED,0BAA0B;QACxB,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,mBAAmB;QACjB,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,QAAgB;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QAC5E,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,kBAAkB,QAAQ,YAAY,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC;QACjC,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,cAAc,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IACjD,CAAC;IAED,kBAAkB;QAChB,OAAO,IAAI,CAAC,qBAAqB,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,WAAmC;QACnD,MAAM,gBAAgB,GAAG,WAAW,IAAI,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAEtE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC;YACvD,KAAK,EAAE,gBAAgB;YACvB,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,QAAgB;QACjC,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;QACpF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;YACrF,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC9B,OAAO;QACT,CAAC;QAED,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE;YACnC,KAAK,EAAE,cAAc,CAAC,KAAK;YAC3B,QAAQ,EAAE,cAAc,CAAC,QAAQ;SAClC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAAE;YAAE,OAAO;QAE/C,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAClD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,gBAAgB,CACpD,CAAC;QAEF,MAAM,SAAS,GAAG,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;QACpE,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAEpD,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC;IAED,cAAc;QACZ,IAAI,CAAC;YACH,MAAM,UAAU,GAAqB;gBACnC,QAAQ,EAAE,IAAI,CAAC,gBAAiB;gBAChC,MAAM,EAAE,IAAI,CAAC,qBAAqB;gBAClC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;aACtB,CAAC;YAEF,YAAY,CAAC,OAAO,CAAC,+BAA+B,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;YAClF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE,UAAU,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED,cAAc;QACZ,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;YACpE,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,UAAU,GAAqB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAEvD,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CACjD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,UAAU,CAAC,QAAQ,CAClD,CAAC;gBAEF,IAAI,gBAAgB,EAAE,CAAC;oBACrB,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAC,QAAQ,CAAC;oBAC5C,IAAI,CAAC,qBAAqB,GAAG,UAAU,CAAC,MAAM,CAAC;oBAC/C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,UAAU,CAAC,CAAC;gBAC9D,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,YAAY,CAAC,MAAmB;QAC9B,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU;YAAE,OAAO,KAAK,CAAC;QAE9B,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;QAC1C,OAAO,QAAQ,CAAC,UAAU,KAAK,aAAa,CAAC;IAC/C,CAAC;IAED,aAAa;QAOX,OAAO;YACL,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACrD,EAAE,EAAE,MAAM,CAAC,QAAQ;gBACnB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,gBAAgB;gBACvC,QAAQ,EAAE,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,gBAAgB;aACpD,CAAC,CAAC;YACH,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,0BAA0B,EAAE,IAAI,CAAC,0BAA0B,EAAE;YAC7D,eAAe,EAAE,IAAI,CAAC,qBAAqB;SAC5C,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,qBAAqB;QACjC,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;gBAC3B,OAAO,QAAQ,CAAC;YAClB,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,QAA0B,EAAE,CAAC,CAAC;YAC3F,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,EAAE,KAAK,CAAC,CAAC;YACpE,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAEO,2BAA2B,CAAC,KAAU;QAC5C,IAAI,CAAC,gBAAgB,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,CAAC,4BAA4B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACtF,CAAC;IAEO,0BAA0B;QAChC,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE/C,IAAI,IAAI,CAAC,eAAe,KAAK,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC;aAAM,IAAI,IAAI,CAAC,eAAe,KAAK,MAAM,EAAE,CAAC;YAC3C,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,qBAAqB,GAAG,MAAM,CAAC;QAEpC,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CACtD,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAC7C,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAC7F,CAAC;QAEF,IAAI,CAAC,gBAAgB,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC/F,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE;YAC/C,KAAK,EAAE,WAAW,EAAE,KAAK,IAAI,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK;YAC3D,QAAQ,EAAE,IAAI,CAAC,gBAAgB;SAChC,CAAC,CAAC;IACL,CAAC;IAEO,gBAAgB;QACtB,IAAI,CAAC,qBAAqB,GAAG,aAAa,CAAC;QAE3C,MAAM,UAAU,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CACrD,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CACnD,CAAC;QAEF,IAAI,CAAC,gBAAgB,GAAG,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC7F,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE;YAC/C,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK;YAC1D,QAAQ,EAAE,IAAI,CAAC,gBAAgB;SAChC,CAAC,CAAC;IACL,CAAC;IAEO,gBAAgB;QACtB,IAAI,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;YACjE,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACzE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;YAC1D,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,EAAE;gBACpD,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK;gBACrC,QAAQ,EAAE,IAAI,CAAC,gBAAgB;aAChC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEO,qBAAqB,CAAC,MAAuB;QACnD,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QACvE,IAAI,CAAC,qBAAqB,GAAG,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC;IACrE,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,IAAI,CAAC;YACH,MAAM,gBAAgB,GAA0B,EAAE,CAAC;YAEnD,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC1B,gBAAgB,CAAC,QAAQ,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC/D,CAAC;iBAAM,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBACtC,gBAAgB,CAAC,UAAU,GAAG,IAAI,CAAC,qBAAqB,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACN,gBAAgB,CAAC,UAAU,GAAG,aAAa,CAAC;YAC9C,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC;gBAC3D,KAAK,EAAE,gBAAgB;aACxB,CAAC,CAAC;YAEH,MAAM,UAAU,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;YAClD,MAAM,YAAY,GAAG,UAAU,CAAC,eAAe,EAAE,CAAC;YAClD,UAAU,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YAEtD,MAAM,WAAW,GAA0B,EAAE,GAAG,gBAAgB,EAAE,CAAC;YAEnE,4CAA4C;YAC5C,IAAI,CAAC,2BAA2B,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;YAE5D,IAAI,YAAY,CAAC,KAAK,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC;gBAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACxD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAE1D,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,UAAU,IAAI,GAAG,CAAC;gBAEvF,IAAI,QAAQ,EAAE,CAAC;oBACb,WAAW,CAAC,KAAK,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;oBACxD,WAAW,CAAC,MAAM,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,CAAC;gBAC3D,CAAC;qBAAM,CAAC;oBACN,WAAW,CAAC,KAAK,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;oBACxC,WAAW,CAAC,MAAM,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;gBAC5C,CAAC;YACH,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE;gBACnD,WAAW;gBACX,YAAY,EAAE,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC;aACxD,CAAC,CAAC;YAEH,OAAO,WAAW,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gFAAgF,CAAC,CAAC;YAEnG,MAAM,QAAQ,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,UAAU,IAAI,GAAG,CAAC;YACvF,MAAM,mBAAmB,GAA0B;gBACjD,KAAK,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE;gBACxC,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE;aACzC,CAAC;YAEF,+CAA+C;YAC/C,IAAI,CAAC,uBAAuB,CAAC,mBAAmB,CAAC,CAAC;YAElD,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC1B,mBAAmB,CAAC,QAAQ,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAClE,CAAC;iBAAM,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC;gBACtC,mBAAmB,CAAC,UAAU,GAAG,IAAI,CAAC,qBAAqB,CAAC;YAC9D,CAAC;iBAAM,CAAC;gBACN,mBAAmB,CAAC,UAAU,GAAG,aAAa,CAAC;YACjD,CAAC;YAED,OAAO,mBAAmB,CAAC;QAC7B,CAAC;IACH,CAAC;IAEO,2BAA2B,CAAC,WAAkC,EAAE,YAAoC;QAC1G,2DAA2D;QAE3D,0BAA0B;QAC1B,IAAI,YAAY,CAAC,SAAS,EAAE,CAAC;YAC3B,WAAW,CAAC,SAAS,GAAG;gBACtB,KAAK,EAAE,EAAE;gBACT,GAAG,EAAE,EAAE;gBACP,GAAG,EAAE,EAAE;aACR,CAAC;QACJ,CAAC;QAED,sDAAsD;QACtD,IAAI,YAAY,CAAC,KAAK,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC;YAC9C,WAAW,CAAC,KAAK,GAAG;gBAClB,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;gBACrD,GAAG,EAAE,GAAG;aACT,CAAC;YACF,WAAW,CAAC,MAAM,GAAG;gBACnB,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC;gBACtD,GAAG,EAAE,GAAG;aACT,CAAC;QACJ,CAAC;QAED,qFAAqF;QACrF,MAAM,mBAAmB,GAA8B,EAAE,CAAC;QAE1D,2DAA2D;QAC3D,MAAM,QAAQ,GAAQ,EAAE,CAAC;QAEzB,+DAA+D;QAC/D,IAAK,YAAoB,CAAC,SAAS,EAAE,CAAC;YACpC,IAAK,YAAoB,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC3D,QAAQ,CAAC,SAAS,GAAG,YAAY,CAAC;YACpC,CAAC;iBAAM,IAAK,YAAoB,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;gBACnE,QAAQ,CAAC,SAAS,GAAG,aAAa,CAAC;YACrC,CAAC;QACH,CAAC;QAED,mCAAmC;QACnC,IAAK,YAAoB,CAAC,aAAa,EAAE,CAAC;YACxC,QAAQ,CAAC,aAAa,GAAG;gBACvB,KAAK,EAAE,GAAG,EAAE,sCAAsC;gBAClD,GAAG,EAAE,GAAG;gBACR,GAAG,EAAE,GAAG;aACT,CAAC;QACJ,CAAC;QAED,eAAe;QACf,IAAK,YAAoB,CAAC,IAAI,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,GAAG;gBACd,KAAK,EAAE,GAAG;gBACV,GAAG,EAAG,YAAoB,CAAC,IAAI,CAAC,GAAG;gBACnC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAE,YAAoB,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC;aACnD,CAAC;QACJ,CAAC;QAED,2CAA2C;QAC3C,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QAED,IAAI,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnC,WAAW,CAAC,QAAQ,GAAG,mBAAmB,CAAC;QAC7C,CAAC;IACH,CAAC;IAEO,uBAAuB,CAAC,WAAkC;QAChE,iEAAiE;QACjE,uDAAuD;QACvD,MAAM,QAAQ,GAAQ;YACpB,SAAS,EAAE,YAAY;YACvB,YAAY,EAAE,YAAY;YAC1B,gBAAgB,EAAE,YAAY;SAC/B,CAAC;QAEF,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;YAC1B,WAAW,CAAC,QAAQ,GAAG,EAAE,CAAC;QAC5B,CAAC;QACD,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEpC,WAAW,CAAC,SAAS,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACxC,CAAC;IAEO,sBAAsB,CAAC,YAAoC;QACjE,OAAO;YACL,YAAY,EAAE,CAAC,CAAE,YAAoB,CAAC,SAAS;YAC/C,gBAAgB,EAAE,CAAC,CAAE,YAAoB,CAAC,aAAa;YACvD,kBAAkB,EAAE,CAAC,CAAE,YAAoB,CAAC,YAAY;YACxD,eAAe,EAAE,CAAC,CAAE,YAAoB,CAAC,gBAAgB;YACzD,OAAO,EAAE,CAAC,CAAE,YAAoB,CAAC,IAAI;YACrC,QAAQ,EAAG,YAAoB,CAAC,KAAK,KAAK,SAAS;YACnD,gBAAgB,EAAE,CAAC,CAAC,CAAE,YAAoB,CAAC,UAAU,IAAK,YAAoB,CAAC,QAAQ,IAAK,YAAoB,CAAC,UAAU,IAAK,YAAoB,CAAC,SAAS,CAAC;YAC/J,eAAe,EAAE,YAAY,CAAC,KAAK,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;gBAC1D,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;SAC5H,CAAC;IACJ,CAAC;IAED,2DAA2D;IAC3D,KAAK,CAAC,eAAe,CAAC,OAAgB,EAAE,MAAoB;QAC1D,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;gBACrE,OAAO,KAAK,CAAC;YACf,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;YAC9C,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;gBACrD,OAAO,KAAK,CAAC;YACf,CAAC;YAED,MAAM,YAAY,GAAG,UAAU,CAAC,eAAe,EAAE,CAAC;YAClD,IAAK,YAAoB,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC9C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;gBAC/D,OAAO,KAAK,CAAC;YACf,CAAC;YAED,MAAM,UAAU,CAAC,gBAAgB,CAAC;gBAChC,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAS,CAAC;aACtC,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;YACzD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,YAAY,CAAC,CAAS,EAAE,CAAS,EAAE,MAAoB;QAC3D,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;gBAC1D,OAAO,KAAK,CAAC;YACf,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;YAC9C,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;gBACrD,OAAO,KAAK,CAAC;YACf,CAAC;YAED,MAAM,YAAY,GAAG,UAAU,CAAC,eAAe,EAAE,CAAC;YAClD,IAAI,CAAE,YAAoB,CAAC,SAAS,IAAI,CAAE,YAAoB,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;gBACjG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;gBAC7D,OAAO,KAAK,CAAC;YACf,CAAC;YAED,4BAA4B;YAC5B,MAAM,UAAU,CAAC,gBAAgB,CAAC;gBAChC,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,aAAa,EAAS,CAAC;aAChD,CAAC,CAAC;YAEH,iDAAiD;YACjD,UAAU,CAAC,KAAK,IAAI,EAAE;gBACpB,IAAI,CAAC;oBACH,MAAM,UAAU,CAAC,gBAAgB,CAAC;wBAChC,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,YAAY,EAAS,CAAC;qBAC/C,CAAC,CAAC;gBACL,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qCAAqC,EAAE,KAAK,CAAC,CAAC;gBACjE,CAAC;YACH,CAAC,EAAE,IAAI,CAAC,CAAC;YAET,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACvD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;YAC1D,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF","sourcesContent":["import { ICameraService, CameraInfo, CameraPreference } from './interfaces/ICameraService';\nimport { ILogger } from './interfaces/ILogger';\nimport { IEventBus } from './interfaces/IEventBus';\n\nexport class CameraService implements ICameraService {\n private availableCameras: MediaDeviceInfo[] = [];\n private selectedCameraId: string | null = null;\n private deviceType: 'mobile' | 'desktop' | 'tablet' = 'desktop';\n private preferredCameraFacing: 'environment' | 'user' | null = null;\n private preferredCamera: 'auto' | 'front' | 'back' = 'auto';\n\n constructor(\n private logger: ILogger,\n private eventBus: IEventBus,\n preferredCamera: 'auto' | 'front' | 'back' = 'auto'\n ) {\n this.preferredCamera = preferredCamera;\n }\n\n async detectDeviceType(): Promise<'mobile' | 'desktop' | 'tablet'> {\n const userAgent = navigator.userAgent;\n const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);\n const isTablet = /iPad|Android/i.test(userAgent) && window.innerWidth >= 768;\n \n if (isTablet) {\n this.deviceType = 'tablet';\n } else if (isMobile) {\n this.deviceType = 'mobile';\n } else {\n this.deviceType = 'desktop';\n }\n \n this.logger.state('DISPOSITIVO_DETECTADO', {\n deviceType: this.deviceType,\n userAgent: navigator.userAgent,\n screenDimensions: { width: window.innerWidth, height: window.innerHeight }\n });\n \n return this.deviceType;\n }\n\n async enumerateDevices(): Promise<MediaDeviceInfo[]> {\n try {\n const permissionStatus = await this.checkCameraPermission();\n \n if (permissionStatus === 'denied') {\n this.logger.error('Permiso de cámara denegado por el usuario');\n return [];\n }\n \n if (permissionStatus === 'prompt') {\n const tempStream = await navigator.mediaDevices.getUserMedia({ video: true });\n tempStream.getTracks().forEach(track => track.stop());\n }\n \n const devices = await navigator.mediaDevices.enumerateDevices();\n this.availableCameras = devices.filter(device => device.kind === 'videoinput');\n \n this.logger.state('CAMARAS_DETECTADAS', {\n count: this.availableCameras.length,\n cameras: this.availableCameras.map(cam => ({\n id: cam.deviceId,\n label: cam.label || 'Unknown Camera'\n }))\n });\n \n this.setInitialCameraPreference();\n this.eventBus.emit('camera-changed', this.selectedCameraId);\n \n return this.availableCameras;\n } catch (error) {\n this.logger.error('Error al enumerar cámaras disponibles:', error);\n this.handleCameraPermissionError(error);\n return [];\n }\n }\n\n getAvailableCameras(): MediaDeviceInfo[] {\n return [...this.availableCameras];\n }\n\n isMultipleCamerasAvailable(): boolean {\n return this.availableCameras.length > 1;\n }\n\n getSelectedCameraId(): string | null {\n return this.selectedCameraId;\n }\n\n async setSelectedCamera(cameraId: string): Promise<void> {\n const camera = this.availableCameras.find(cam => cam.deviceId === cameraId);\n if (!camera) {\n throw new Error(`Camera with ID ${cameraId} not found`);\n }\n \n this.selectedCameraId = cameraId;\n this.updatePreferredFacing(camera);\n this.savePreference();\n this.eventBus.emit('camera-changed', cameraId);\n }\n\n getPreferredFacing(): 'environment' | 'user' | null {\n return this.preferredCameraFacing;\n }\n\n async setupCamera(constraints?: MediaTrackConstraints): Promise<MediaStream> {\n const finalConstraints = constraints || await this.getMaxResolution();\n \n const stream = await navigator.mediaDevices.getUserMedia({\n video: finalConstraints,\n audio: false\n });\n\n return stream;\n }\n\n async switchCamera(cameraId: string): Promise<void> {\n const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);\n if (!selectedCamera) {\n this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');\n await this.enumerateDevices();\n return;\n }\n \n await this.setSelectedCamera(cameraId);\n this.logger.state('CAMARA_CAMBIADA', { \n label: selectedCamera.label, \n deviceId: selectedCamera.deviceId \n });\n }\n\n async flipToNextCamera(): Promise<void> {\n if (!this.isMultipleCamerasAvailable()) return;\n \n const currentIndex = this.availableCameras.findIndex(\n camera => camera.deviceId === this.selectedCameraId\n );\n \n const nextIndex = (currentIndex + 1) % this.availableCameras.length;\n const nextCamera = this.availableCameras[nextIndex];\n \n await this.switchCamera(nextCamera.deviceId);\n }\n\n savePreference(): void {\n try {\n const preference: CameraPreference = {\n cameraId: this.selectedCameraId!,\n facing: this.preferredCameraFacing,\n timestamp: Date.now()\n };\n \n localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));\n this.logger.state('PREFERENCIA_CAMARA_GUARDADA', preference);\n } catch (error) {\n this.logger.warn('Error al guardar preferencia de cámara:', error);\n }\n }\n\n loadPreference(): void {\n try {\n const saved = localStorage.getItem('jaak-stamps-camera-preference');\n if (saved) {\n const preference: CameraPreference = JSON.parse(saved);\n \n const isStillAvailable = this.availableCameras.some(\n camera => camera.deviceId === preference.cameraId\n );\n \n if (isStillAvailable) {\n this.selectedCameraId = preference.cameraId;\n this.preferredCameraFacing = preference.facing;\n this.logger.state('PREFERENCIA_CAMARA_CARGADA', preference);\n }\n }\n } catch (error) {\n this.logger.warn('Error al cargar preferencia de cámara:', error);\n }\n }\n\n isRearCamera(stream: MediaStream): boolean {\n const videoTrack = stream.getVideoTracks()[0];\n if (!videoTrack) return false;\n \n const settings = videoTrack.getSettings();\n return settings.facingMode === 'environment';\n }\n\n getCameraInfo(): {\n availableCameras: CameraInfo[];\n selectedCameraId: string | null;\n deviceType: string;\n isMultipleCamerasAvailable: boolean;\n preferredFacing: 'environment' | 'user' | null;\n } {\n return {\n availableCameras: this.availableCameras.map(camera => ({\n id: camera.deviceId,\n label: camera.label || 'Unknown Camera',\n selected: camera.deviceId === this.selectedCameraId\n })),\n selectedCameraId: this.selectedCameraId,\n deviceType: this.deviceType,\n isMultipleCamerasAvailable: this.isMultipleCamerasAvailable(),\n preferredFacing: this.preferredCameraFacing\n };\n }\n\n private async checkCameraPermission(): Promise<'granted' | 'denied' | 'prompt'> {\n try {\n if (!navigator.permissions) {\n return 'prompt';\n }\n \n const permission = await navigator.permissions.query({ name: 'camera' as PermissionName });\n return permission.state;\n } catch (error) {\n this.logger.warn('No se pudo verificar permisos de cámara:', error);\n return 'prompt';\n }\n }\n\n private handleCameraPermissionError(error: any): void {\n this.availableCameras = [];\n this.eventBus.emit('error', new Error(`Camera permission error: ${error.message}`));\n }\n\n private setInitialCameraPreference(): void {\n if (this.availableCameras.length === 0) return;\n \n if (this.preferredCamera === 'front') {\n this.selectFrontCamera();\n } else if (this.preferredCamera === 'back') {\n this.selectBackCamera();\n } else {\n this.selectAutoCamera();\n }\n }\n\n private selectFrontCamera(): void {\n this.preferredCameraFacing = 'user';\n \n const frontCamera = this.availableCameras.find(camera => \n camera.label.toLowerCase().includes('front') || \n camera.label.toLowerCase().includes('user') ||\n camera.label.toLowerCase().includes('selfie') ||\n !camera.label.toLowerCase().includes('back') && !camera.label.toLowerCase().includes('rear')\n );\n \n this.selectedCameraId = frontCamera ? frontCamera.deviceId : this.availableCameras[0].deviceId;\n this.logger.state('CAMARA_FRONTAL_SELECCIONADA', { \n label: frontCamera?.label || this.availableCameras[0].label,\n deviceId: this.selectedCameraId \n });\n }\n\n private selectBackCamera(): void {\n this.preferredCameraFacing = 'environment';\n \n const backCamera = this.availableCameras.find(camera => \n camera.label.toLowerCase().includes('back') || \n camera.label.toLowerCase().includes('rear') ||\n camera.label.toLowerCase().includes('environment')\n );\n \n this.selectedCameraId = backCamera ? backCamera.deviceId : this.availableCameras[0].deviceId;\n this.logger.state('CAMARA_TRASERA_SELECCIONADA', { \n label: backCamera?.label || this.availableCameras[0].label,\n deviceId: this.selectedCameraId \n });\n }\n\n private selectAutoCamera(): void {\n if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {\n this.selectBackCamera();\n this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });\n } else {\n this.selectedCameraId = this.availableCameras[0].deviceId;\n this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', { \n label: this.availableCameras[0].label, \n deviceId: this.selectedCameraId \n });\n }\n }\n\n private updatePreferredFacing(camera: MediaDeviceInfo): void {\n const isRearCamera = camera.label.toLowerCase().includes('back') || \n camera.label.toLowerCase().includes('rear') ||\n camera.label.toLowerCase().includes('environment');\n this.preferredCameraFacing = isRearCamera ? 'environment' : 'user';\n }\n\n private async getMaxResolution(): Promise<MediaTrackConstraints> {\n try {\n const videoConstraints: MediaTrackConstraints = {};\n \n if (this.selectedCameraId) {\n videoConstraints.deviceId = { exact: this.selectedCameraId };\n } else if (this.preferredCameraFacing) {\n videoConstraints.facingMode = this.preferredCameraFacing;\n } else {\n videoConstraints.facingMode = \"environment\";\n }\n \n const tempStream = await navigator.mediaDevices.getUserMedia({\n video: videoConstraints\n });\n \n const videoTrack = tempStream.getVideoTracks()[0];\n const capabilities = videoTrack.getCapabilities();\n tempStream.getTracks().forEach(track => track.stop());\n \n const constraints: MediaTrackConstraints = { ...videoConstraints };\n \n // Enhanced focus and image quality settings\n this.applyAdvancedCameraSettings(constraints, capabilities);\n \n if (capabilities.width && capabilities.height) {\n const maxWidth = Math.min(capabilities.width.max, 1920);\n const maxHeight = Math.min(capabilities.height.max, 1080);\n \n const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;\n \n if (isTablet) {\n constraints.width = { ideal: Math.min(maxWidth, 1280) };\n constraints.height = { ideal: Math.min(maxHeight, 720) };\n } else {\n constraints.width = { ideal: maxWidth };\n constraints.height = { ideal: maxHeight };\n }\n }\n \n this.logger.state('CONFIGURACION_CAMARA_OPTIMIZADA', {\n constraints,\n capabilities: this.getCapabilitiesSummary(capabilities)\n });\n \n return constraints;\n } catch (err) {\n this.logger.warn('No se pudieron obtener capacidades de cámara, usando configuración de respaldo');\n \n const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;\n const fallbackConstraints: MediaTrackConstraints = {\n width: { ideal: isTablet ? 1280 : 1920 },\n height: { ideal: isTablet ? 720 : 1080 }\n };\n \n // Apply basic focus settings even for fallback\n this.applyBasicFocusSettings(fallbackConstraints);\n \n if (this.selectedCameraId) {\n fallbackConstraints.deviceId = { exact: this.selectedCameraId };\n } else if (this.preferredCameraFacing) {\n fallbackConstraints.facingMode = this.preferredCameraFacing;\n } else {\n fallbackConstraints.facingMode = \"environment\";\n }\n \n return fallbackConstraints;\n }\n }\n\n private applyAdvancedCameraSettings(constraints: MediaTrackConstraints, capabilities: MediaTrackCapabilities): void {\n // Apply standard camera settings that are widely supported\n \n // Frame rate optimization\n if (capabilities.frameRate) {\n constraints.frameRate = {\n ideal: 30,\n min: 15,\n max: 60\n };\n }\n\n // Width and height constraints for optimal resolution\n if (capabilities.width && capabilities.height) {\n constraints.width = {\n ideal: Math.min(1920, capabilities.width.max || 1920),\n min: 640\n };\n constraints.height = {\n ideal: Math.min(1080, capabilities.height.max || 1080),\n min: 480\n };\n }\n\n // Apply enhanced constraints through advanced array for better browser compatibility\n const advancedConstraints: MediaTrackConstraintSet[] = [];\n \n // Try to set optimal settings through advanced constraints\n const advanced: any = {};\n \n // Focus mode - use 'any' type to bypass TypeScript limitations\n if ((capabilities as any).focusMode) {\n if ((capabilities as any).focusMode.includes('continuous')) {\n advanced.focusMode = 'continuous';\n } else if ((capabilities as any).focusMode.includes('single-shot')) {\n advanced.focusMode = 'single-shot';\n }\n }\n\n // Focus distance for close objects\n if ((capabilities as any).focusDistance) {\n advanced.focusDistance = {\n ideal: 0.4, // 40cm - optimal for document capture\n min: 0.2,\n max: 1.0\n };\n }\n\n // Zoom control\n if ((capabilities as any).zoom) {\n advanced.zoom = {\n ideal: 1.0,\n min: (capabilities as any).zoom.min,\n max: Math.min((capabilities as any).zoom.max, 3.0)\n };\n }\n\n // Add advanced constraints if any were set\n if (Object.keys(advanced).length > 0) {\n advancedConstraints.push(advanced);\n }\n\n if (advancedConstraints.length > 0) {\n constraints.advanced = advancedConstraints;\n }\n }\n\n private applyBasicFocusSettings(constraints: MediaTrackConstraints): void {\n // Apply basic focus settings when capabilities are not available\n // Use advanced constraints for non-standard properties\n const advanced: any = {\n focusMode: 'continuous',\n exposureMode: 'continuous',\n whiteBalanceMode: 'continuous'\n };\n \n if (!constraints.advanced) {\n constraints.advanced = [];\n }\n constraints.advanced.push(advanced);\n \n constraints.frameRate = { ideal: 30 };\n }\n\n private getCapabilitiesSummary(capabilities: MediaTrackCapabilities): any {\n return {\n hasAutoFocus: !!(capabilities as any).focusMode,\n hasFocusDistance: !!(capabilities as any).focusDistance,\n hasExposureControl: !!(capabilities as any).exposureMode,\n hasWhiteBalance: !!(capabilities as any).whiteBalanceMode,\n hasZoom: !!(capabilities as any).zoom,\n hasTorch: (capabilities as any).torch !== undefined,\n hasImageControls: !!((capabilities as any).brightness || (capabilities as any).contrast || (capabilities as any).saturation || (capabilities as any).sharpness),\n resolutionRange: capabilities.width && capabilities.height ? \n `${capabilities.width.min}x${capabilities.height.min} - ${capabilities.width.max}x${capabilities.height.max}` : 'unknown'\n };\n }\n\n // New method to enable torch/flash for better illumination\n async setTorchEnabled(enabled: boolean, stream?: MediaStream): Promise<boolean> {\n try {\n if (!stream) {\n this.logger.warn('No hay stream disponible para controlar el flash');\n return false;\n }\n\n const videoTrack = stream.getVideoTracks()[0];\n if (!videoTrack) {\n this.logger.warn('No hay track de video disponible');\n return false;\n }\n\n const capabilities = videoTrack.getCapabilities();\n if ((capabilities as any).torch === undefined) {\n this.logger.warn('El dispositivo no soporta control de flash');\n return false;\n }\n\n await videoTrack.applyConstraints({\n advanced: [{ torch: enabled } as any]\n });\n\n this.logger.state('FLASH_CONTROLADO', { enabled });\n return true;\n } catch (error) {\n this.logger.error('Error al controlar el flash:', error);\n return false;\n }\n }\n\n // Method to manually trigger focus on a specific point\n async focusAtPoint(x: number, y: number, stream?: MediaStream): Promise<boolean> {\n try {\n if (!stream) {\n this.logger.warn('No hay stream disponible para enfocar');\n return false;\n }\n\n const videoTrack = stream.getVideoTracks()[0];\n if (!videoTrack) {\n this.logger.warn('No hay track de video disponible');\n return false;\n }\n\n const capabilities = videoTrack.getCapabilities();\n if (!(capabilities as any).focusMode || !(capabilities as any).focusMode.includes('single-shot')) {\n this.logger.warn('El dispositivo no soporta enfoque manual');\n return false;\n }\n\n // Trigger single-shot focus\n await videoTrack.applyConstraints({\n advanced: [{ focusMode: 'single-shot' } as any]\n });\n\n // Return to continuous focus after a short delay\n setTimeout(async () => {\n try {\n await videoTrack.applyConstraints({\n advanced: [{ focusMode: 'continuous' } as any]\n });\n } catch (error) {\n this.logger.warn('Error al volver a enfoque continuo:', error);\n }\n }, 1000);\n\n this.logger.state('ENFOQUE_MANUAL_ACTIVADO', { x, y });\n return true;\n } catch (error) {\n this.logger.error('Error al enfocar manualmente:', error);\n return false;\n }\n }\n}"]}
|