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