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