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