@capgo/camera-preview 8.2.1 → 8.3.0
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 +31 -29
- package/android/build.gradle +4 -0
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraPreview.java +87 -20
- package/android/src/main/java/app/capgo/capacitor/camera/preview/CameraXView.java +665 -188
- package/android/src/main/java/app/capgo/capacitor/camera/preview/model/CameraSessionConfiguration.java +13 -0
- package/dist/docs.json +17 -1
- package/dist/esm/definitions.d.ts +9 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +1 -1
- package/dist/esm/web.js +120 -92
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +119 -92
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +119 -92
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapgoCameraPreviewPlugin/CameraController.swift +14 -12
- package/ios/Sources/CapgoCameraPreviewPlugin/Plugin.swift +16 -14
- package/package.json +9 -8
|
@@ -96,11 +96,14 @@ import java.util.Collections;
|
|
|
96
96
|
import java.util.Date;
|
|
97
97
|
import java.util.List;
|
|
98
98
|
import java.util.Locale;
|
|
99
|
+
import java.util.Map;
|
|
99
100
|
import java.util.Objects;
|
|
100
101
|
import java.util.Set;
|
|
102
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
101
103
|
import java.util.concurrent.Executor;
|
|
102
104
|
import java.util.concurrent.ExecutorService;
|
|
103
105
|
import java.util.concurrent.Executors;
|
|
106
|
+
import java.util.concurrent.RejectedExecutionException;
|
|
104
107
|
import org.json.JSONObject;
|
|
105
108
|
|
|
106
109
|
public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
@@ -138,6 +141,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
138
141
|
private long focusIndicatorAnimationId = 0; // Incrementing token to invalidate previous animations
|
|
139
142
|
private CameraSelector currentCameraSelector;
|
|
140
143
|
private String currentDeviceId;
|
|
144
|
+
private String currentPhysicalDeviceId;
|
|
145
|
+
private String currentLogicalDeviceId;
|
|
141
146
|
private int currentFlashMode = ImageCapture.FLASH_MODE_OFF;
|
|
142
147
|
private CameraSessionConfiguration sessionConfig;
|
|
143
148
|
private CameraXViewListener listener;
|
|
@@ -150,6 +155,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
150
155
|
private final LifecycleRegistry lifecycleRegistry;
|
|
151
156
|
private final Executor mainExecutor;
|
|
152
157
|
private ExecutorService cameraExecutor;
|
|
158
|
+
private static volatile Map<String, app.capgo.capacitor.camera.preview.model.CameraDevice> enumeratedDeviceCache =
|
|
159
|
+
new ConcurrentHashMap<>();
|
|
160
|
+
private static final Object enumeratedDeviceCacheLock = new Object();
|
|
161
|
+
private static volatile boolean enumeratedDeviceCacheRefreshInProgress = false;
|
|
153
162
|
private boolean isRunning = false;
|
|
154
163
|
private Size currentPreviewResolution = null;
|
|
155
164
|
private ListenableFuture<FocusMeteringResult> currentFocusFuture = null; // Track current focus operation
|
|
@@ -213,6 +222,56 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
213
222
|
}
|
|
214
223
|
};
|
|
215
224
|
|
|
225
|
+
private static final class PhysicalCameraBindingTarget {
|
|
226
|
+
|
|
227
|
+
private final CameraInfo logicalCameraInfo;
|
|
228
|
+
private final String logicalCameraId;
|
|
229
|
+
private final int requiredFacing;
|
|
230
|
+
|
|
231
|
+
private PhysicalCameraBindingTarget(CameraInfo logicalCameraInfo, String logicalCameraId, int requiredFacing) {
|
|
232
|
+
this.logicalCameraInfo = logicalCameraInfo;
|
|
233
|
+
this.logicalCameraId = logicalCameraId;
|
|
234
|
+
this.requiredFacing = requiredFacing;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private static final class PhysicalDeviceMetadata {
|
|
239
|
+
|
|
240
|
+
private final String position;
|
|
241
|
+
private final float fallbackZoom;
|
|
242
|
+
|
|
243
|
+
private PhysicalDeviceMetadata(String position, float fallbackZoom) {
|
|
244
|
+
this.position = position;
|
|
245
|
+
this.fallbackZoom = fallbackZoom;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private static final class CameraBindingPlan {
|
|
250
|
+
|
|
251
|
+
private final CameraSelector selector;
|
|
252
|
+
private final String reportedDeviceId;
|
|
253
|
+
private final String logicalCameraId;
|
|
254
|
+
private final String physicalCameraId;
|
|
255
|
+
private final float fallbackZoom;
|
|
256
|
+
private final boolean usesPhysicalSelection;
|
|
257
|
+
|
|
258
|
+
private CameraBindingPlan(
|
|
259
|
+
CameraSelector selector,
|
|
260
|
+
String reportedDeviceId,
|
|
261
|
+
String logicalCameraId,
|
|
262
|
+
String physicalCameraId,
|
|
263
|
+
float fallbackZoom,
|
|
264
|
+
boolean usesPhysicalSelection
|
|
265
|
+
) {
|
|
266
|
+
this.selector = selector;
|
|
267
|
+
this.reportedDeviceId = reportedDeviceId;
|
|
268
|
+
this.logicalCameraId = logicalCameraId;
|
|
269
|
+
this.physicalCameraId = physicalCameraId;
|
|
270
|
+
this.fallbackZoom = fallbackZoom;
|
|
271
|
+
this.usesPhysicalSelection = usesPhysicalSelection;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
216
275
|
private boolean IsOperationRunning(String name) {
|
|
217
276
|
synchronized (operationLock) {
|
|
218
277
|
if (stopPending) {
|
|
@@ -268,7 +327,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
268
327
|
this.lifecycleRegistry = new LifecycleRegistry(this);
|
|
269
328
|
this.mainExecutor = ContextCompat.getMainExecutor(context);
|
|
270
329
|
|
|
271
|
-
mainExecutor.execute(() ->
|
|
330
|
+
mainExecutor.execute(() -> {
|
|
331
|
+
if (lifecycleRegistry.getCurrentState() != Lifecycle.State.DESTROYED) {
|
|
332
|
+
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
272
335
|
}
|
|
273
336
|
|
|
274
337
|
@NonNull
|
|
@@ -390,36 +453,92 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
390
453
|
}
|
|
391
454
|
|
|
392
455
|
public void startSession(CameraSessionConfiguration config) {
|
|
393
|
-
this.sessionConfig = config;
|
|
394
|
-
cameraExecutor = Executors.newSingleThreadExecutor();
|
|
395
|
-
|
|
396
|
-
// Reset cached orientation so we don't reuse stale values across sessions
|
|
397
|
-
synchronized (accelerometerLock) {
|
|
398
|
-
lastAccelerometerValues[0] = 0f;
|
|
399
|
-
lastAccelerometerValues[1] = 0f;
|
|
400
|
-
lastAccelerometerValues[2] = 0f;
|
|
401
|
-
}
|
|
402
|
-
lastCaptureRotation = -1;
|
|
403
|
-
|
|
404
|
-
// Start accelerometer for orientation detection regardless of lock
|
|
405
|
-
if (sensorManager == null) {
|
|
406
|
-
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
|
407
|
-
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
|
408
|
-
rotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
|
|
409
|
-
}
|
|
410
|
-
if (accelerometer != null) {
|
|
411
|
-
sensorManager.registerListener(accelerometerListener, accelerometer, SensorManager.SENSOR_DELAY_UI);
|
|
412
|
-
}
|
|
413
|
-
if (rotationVectorSensor != null) {
|
|
414
|
-
sensorManager.registerListener(rotationVectorListener, rotationVectorSensor, SensorManager.SENSOR_DELAY_NORMAL);
|
|
415
|
-
}
|
|
416
|
-
lastCompassHeading = -1f;
|
|
417
|
-
synchronized (operationLock) {
|
|
418
|
-
activeOperations = 0;
|
|
419
|
-
stopPending = false;
|
|
420
|
-
}
|
|
421
456
|
mainExecutor.execute(() -> {
|
|
457
|
+
// Stop may run first (e.g. activity pause) and move the registry to DESTROYED while this
|
|
458
|
+
// runnable is still queued — never transition backward from DESTROYED.
|
|
459
|
+
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
460
|
+
if (listener != null) {
|
|
461
|
+
listener.onCameraStartError("Camera start aborted: lifecycle destroyed");
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (stopRequested) {
|
|
466
|
+
if (listener != null) {
|
|
467
|
+
listener.onCameraStartError("Camera start aborted: stop requested");
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
Lifecycle.State state = lifecycleRegistry.getCurrentState();
|
|
472
|
+
if (state == Lifecycle.State.INITIALIZED) {
|
|
473
|
+
lifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
|
|
474
|
+
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
475
|
+
if (listener != null) {
|
|
476
|
+
listener.onCameraStartError("Camera start aborted: lifecycle destroyed");
|
|
477
|
+
}
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (stopRequested) {
|
|
481
|
+
if (listener != null) {
|
|
482
|
+
listener.onCameraStartError("Camera start aborted: stop requested");
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
488
|
+
if (listener != null) {
|
|
489
|
+
listener.onCameraStartError("Camera start aborted: lifecycle destroyed");
|
|
490
|
+
}
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (stopRequested) {
|
|
494
|
+
if (listener != null) {
|
|
495
|
+
listener.onCameraStartError("Camera start aborted: stop requested");
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
422
499
|
lifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
|
|
500
|
+
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
501
|
+
if (listener != null) {
|
|
502
|
+
listener.onCameraStartError("Camera start aborted: lifecycle destroyed");
|
|
503
|
+
}
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (stopRequested) {
|
|
507
|
+
if (listener != null) {
|
|
508
|
+
listener.onCameraStartError("Camera start aborted: stop requested");
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
this.sessionConfig = config;
|
|
514
|
+
cameraExecutor = Executors.newSingleThreadExecutor();
|
|
515
|
+
requestEnumeratedDeviceCacheRefresh();
|
|
516
|
+
|
|
517
|
+
// Reset cached orientation so we don't reuse stale values across sessions
|
|
518
|
+
synchronized (accelerometerLock) {
|
|
519
|
+
lastAccelerometerValues[0] = 0f;
|
|
520
|
+
lastAccelerometerValues[1] = 0f;
|
|
521
|
+
lastAccelerometerValues[2] = 0f;
|
|
522
|
+
}
|
|
523
|
+
lastCaptureRotation = -1;
|
|
524
|
+
|
|
525
|
+
// Start accelerometer for orientation detection regardless of lock
|
|
526
|
+
if (sensorManager == null) {
|
|
527
|
+
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
|
528
|
+
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
|
529
|
+
rotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
|
|
530
|
+
}
|
|
531
|
+
if (accelerometer != null) {
|
|
532
|
+
sensorManager.registerListener(accelerometerListener, accelerometer, SensorManager.SENSOR_DELAY_UI);
|
|
533
|
+
}
|
|
534
|
+
if (rotationVectorSensor != null) {
|
|
535
|
+
sensorManager.registerListener(rotationVectorListener, rotationVectorSensor, SensorManager.SENSOR_DELAY_NORMAL);
|
|
536
|
+
}
|
|
537
|
+
lastCompassHeading = -1f;
|
|
538
|
+
synchronized (operationLock) {
|
|
539
|
+
activeOperations = 0;
|
|
540
|
+
stopPending = false;
|
|
541
|
+
}
|
|
423
542
|
setupCamera();
|
|
424
543
|
});
|
|
425
544
|
}
|
|
@@ -464,6 +583,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
464
583
|
|
|
465
584
|
private void performImmediateStop() {
|
|
466
585
|
isRunning = false;
|
|
586
|
+
currentDeviceId = null;
|
|
587
|
+
currentPhysicalDeviceId = null;
|
|
588
|
+
currentLogicalDeviceId = null;
|
|
467
589
|
// Stop accelerometer and rotation vector sensor
|
|
468
590
|
if (sensorManager != null && accelerometer != null) {
|
|
469
591
|
sensorManager.unregisterListener(accelerometerListener);
|
|
@@ -483,7 +605,6 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
483
605
|
if (cameraProvider != null) {
|
|
484
606
|
cameraProvider.unbindAll();
|
|
485
607
|
}
|
|
486
|
-
lifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
|
|
487
608
|
if (cameraExecutor != null) {
|
|
488
609
|
cameraExecutor.shutdown();
|
|
489
610
|
}
|
|
@@ -527,7 +648,31 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
527
648
|
cameraProviderFuture.addListener(
|
|
528
649
|
() -> {
|
|
529
650
|
try {
|
|
651
|
+
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
652
|
+
if (listener != null) {
|
|
653
|
+
listener.onCameraStartError("Camera binding cancelled: lifecycle destroyed (before provider)");
|
|
654
|
+
}
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (stopRequested) {
|
|
658
|
+
if (listener != null) {
|
|
659
|
+
listener.onCameraStartError("Camera binding cancelled: stop requested (before provider)");
|
|
660
|
+
}
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
530
663
|
cameraProvider = cameraProviderFuture.get();
|
|
664
|
+
if (lifecycleRegistry.getCurrentState() == Lifecycle.State.DESTROYED) {
|
|
665
|
+
if (listener != null) {
|
|
666
|
+
listener.onCameraStartError("Camera binding cancelled: lifecycle destroyed (after provider)");
|
|
667
|
+
}
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (stopRequested) {
|
|
671
|
+
if (listener != null) {
|
|
672
|
+
listener.onCameraStartError("Camera binding cancelled: stop requested (after provider)");
|
|
673
|
+
}
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
531
676
|
setupPreviewView();
|
|
532
677
|
bindCameraUseCases();
|
|
533
678
|
} catch (Exception e) {
|
|
@@ -662,6 +807,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
662
807
|
int width = sessionConfig.getWidth();
|
|
663
808
|
int height = sessionConfig.getHeight();
|
|
664
809
|
String aspectRatio = sessionConfig.getAspectRatio();
|
|
810
|
+
String aspectMode = sessionConfig.getAspectMode();
|
|
665
811
|
|
|
666
812
|
// Get comprehensive display information
|
|
667
813
|
int screenWidthPx, screenHeightPx;
|
|
@@ -731,7 +877,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
731
877
|
);
|
|
732
878
|
|
|
733
879
|
// Apply aspect ratio if specified
|
|
734
|
-
if (aspectRatio != null && !aspectRatio.isEmpty() && sessionConfig.isCentered()) {
|
|
880
|
+
if (aspectRatio != null && !aspectRatio.isEmpty() && sessionConfig.isCentered() && !"cover".equals(aspectMode)) {
|
|
735
881
|
String[] ratios = aspectRatio.split(":");
|
|
736
882
|
if (ratios.length == 2) {
|
|
737
883
|
try {
|
|
@@ -846,7 +992,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
846
992
|
" and position: " +
|
|
847
993
|
sessionConfig.getPosition()
|
|
848
994
|
);
|
|
849
|
-
|
|
995
|
+
CameraBindingPlan bindingPlan = buildCameraBindingPlan(sessionConfig);
|
|
996
|
+
currentCameraSelector = bindingPlan.selector;
|
|
850
997
|
|
|
851
998
|
ResolutionSelector.Builder resolutionSelectorBuilder = new ResolutionSelector.Builder().setResolutionStrategy(
|
|
852
999
|
ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY
|
|
@@ -924,17 +1071,24 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
924
1071
|
// is connected and video frames are captured correctly
|
|
925
1072
|
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
926
1073
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1074
|
+
try {
|
|
1075
|
+
bindConfiguredUseCases(bindingPlan, preview);
|
|
1076
|
+
} catch (Exception initialBindError) {
|
|
1077
|
+
if (!bindingPlan.usesPhysicalSelection) {
|
|
1078
|
+
throw initialBindError;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
Log.w(
|
|
1082
|
+
TAG,
|
|
1083
|
+
"bindCameraUseCases: Physical camera binding failed for " +
|
|
1084
|
+
bindingPlan.physicalCameraId +
|
|
1085
|
+
", falling back to logical camera behavior",
|
|
1086
|
+
initialBindError
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
bindingPlan = buildLogicalFallbackPlan(sessionConfig, bindingPlan);
|
|
1090
|
+
currentCameraSelector = bindingPlan.selector;
|
|
1091
|
+
bindConfiguredUseCases(bindingPlan, preview);
|
|
938
1092
|
}
|
|
939
1093
|
|
|
940
1094
|
resetExposureCompensationToDefault();
|
|
@@ -942,7 +1096,10 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
942
1096
|
// Log details about the active camera
|
|
943
1097
|
Log.d(TAG, "Use cases bound. Inspecting active camera and use cases.");
|
|
944
1098
|
CameraInfo cameraInfo = camera.getCameraInfo();
|
|
945
|
-
Log.d(TAG, "Bound Camera ID: " +
|
|
1099
|
+
Log.d(TAG, "Bound Camera ID: " + currentLogicalDeviceId);
|
|
1100
|
+
if (currentPhysicalDeviceId != null) {
|
|
1101
|
+
Log.d(TAG, "Bound Physical Camera ID: " + currentPhysicalDeviceId);
|
|
1102
|
+
}
|
|
946
1103
|
|
|
947
1104
|
// Log zoom state
|
|
948
1105
|
ZoomState zoomState = cameraInfo.getZoomState().getValue();
|
|
@@ -1018,7 +1175,11 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1018
1175
|
previewView.setScaleType("cover".equals(aspectMode) ? PreviewView.ScaleType.FILL_CENTER : PreviewView.ScaleType.FIT_CENTER);
|
|
1019
1176
|
|
|
1020
1177
|
// Set initial zoom if specified, prioritizing targetZoom over default zoomFactor
|
|
1021
|
-
float initialZoom =
|
|
1178
|
+
float initialZoom = !bindingPlan.usesPhysicalSelection &&
|
|
1179
|
+
bindingPlan.fallbackZoom != 1.0f &&
|
|
1180
|
+
sessionConfig.getTargetZoom() == 1.0f
|
|
1181
|
+
? bindingPlan.fallbackZoom
|
|
1182
|
+
: (sessionConfig.getTargetZoom() != 1.0f ? sessionConfig.getTargetZoom() : sessionConfig.getZoomFactor());
|
|
1022
1183
|
if (initialZoom != 1.0f) {
|
|
1023
1184
|
Log.d(TAG, "Applying initial zoom of " + initialZoom);
|
|
1024
1185
|
|
|
@@ -1088,24 +1249,320 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
1088
1249
|
|
|
1089
1250
|
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
1090
1251
|
private CameraSelector buildCameraSelector() {
|
|
1091
|
-
|
|
1092
|
-
|
|
1252
|
+
return buildCameraBindingPlan(sessionConfig).selector;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
1256
|
+
private CameraBindingPlan buildCameraBindingPlan(CameraSessionConfiguration config) {
|
|
1257
|
+
final String deviceId = config.getDeviceId();
|
|
1258
|
+
final String position = config.getPosition();
|
|
1093
1259
|
|
|
1094
1260
|
if (deviceId != null && !deviceId.isEmpty()) {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1261
|
+
CameraInfo directCameraInfo = findAvailableCameraInfoById(deviceId);
|
|
1262
|
+
if (directCameraInfo != null) {
|
|
1263
|
+
CameraSelector.Builder directBuilder = new CameraSelector.Builder();
|
|
1264
|
+
directBuilder.addCameraFilter((cameraInfos) -> {
|
|
1265
|
+
for (CameraInfo cameraInfo : cameraInfos) {
|
|
1266
|
+
if (deviceId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())) {
|
|
1267
|
+
return Collections.singletonList(cameraInfo);
|
|
1268
|
+
}
|
|
1099
1269
|
}
|
|
1270
|
+
return Collections.emptyList();
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
return new CameraBindingPlan(directBuilder.build(), deviceId, deviceId, null, 1.0f, false);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
CameraBindingPlan logicalFallbackPlan = buildLogicalFallbackPlanForDeviceId(deviceId);
|
|
1277
|
+
if (config.isPhysicalDeviceSelectionEnabled()) {
|
|
1278
|
+
PhysicalCameraBindingTarget physicalTarget = findPhysicalCameraBindingTarget(deviceId);
|
|
1279
|
+
if (physicalTarget != null) {
|
|
1280
|
+
CameraSelector.Builder physicalBuilder = new CameraSelector.Builder()
|
|
1281
|
+
.requireLensFacing(physicalTarget.requiredFacing)
|
|
1282
|
+
.setPhysicalCameraId(deviceId)
|
|
1283
|
+
.addCameraFilter((cameraInfos) -> {
|
|
1284
|
+
for (CameraInfo cameraInfo : cameraInfos) {
|
|
1285
|
+
if (physicalTarget.logicalCameraId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())) {
|
|
1286
|
+
return Collections.singletonList(cameraInfo);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
return Collections.emptyList();
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
return new CameraBindingPlan(
|
|
1293
|
+
physicalBuilder.build(),
|
|
1294
|
+
deviceId,
|
|
1295
|
+
physicalTarget.logicalCameraId,
|
|
1296
|
+
deviceId,
|
|
1297
|
+
logicalFallbackPlan != null ? logicalFallbackPlan.fallbackZoom : getFallbackZoomForDeviceId(deviceId),
|
|
1298
|
+
true
|
|
1299
|
+
);
|
|
1100
1300
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
if (logicalFallbackPlan != null) {
|
|
1304
|
+
return logicalFallbackPlan;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
throw invalidDeviceId(deviceId);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
return buildPositionPlan(position);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
private CameraBindingPlan buildLogicalFallbackPlan(CameraSessionConfiguration config, CameraBindingPlan failedPhysicalPlan) {
|
|
1314
|
+
String fallbackPosition = config.getPosition();
|
|
1315
|
+
|
|
1316
|
+
if (failedPhysicalPlan.logicalCameraId != null) {
|
|
1317
|
+
CameraInfo logicalCameraInfo = findAvailableCameraInfoById(failedPhysicalPlan.logicalCameraId);
|
|
1318
|
+
if (logicalCameraInfo != null) {
|
|
1319
|
+
fallbackPosition = isBackCamera(logicalCameraInfo) ? "rear" : "front";
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
CameraBindingPlan positionPlan = buildPositionPlan(fallbackPosition);
|
|
1324
|
+
return new CameraBindingPlan(
|
|
1325
|
+
positionPlan.selector,
|
|
1326
|
+
failedPhysicalPlan.reportedDeviceId,
|
|
1327
|
+
positionPlan.logicalCameraId,
|
|
1328
|
+
null,
|
|
1329
|
+
failedPhysicalPlan.fallbackZoom,
|
|
1330
|
+
false
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
private CameraBindingPlan buildLogicalFallbackPlanForDeviceId(String deviceId) {
|
|
1335
|
+
String fallbackPosition = resolveFallbackPositionForDeviceId(deviceId);
|
|
1336
|
+
if (fallbackPosition == null) {
|
|
1337
|
+
return null;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
CameraBindingPlan positionPlan = buildPositionPlan(fallbackPosition);
|
|
1341
|
+
return new CameraBindingPlan(
|
|
1342
|
+
positionPlan.selector,
|
|
1343
|
+
deviceId,
|
|
1344
|
+
positionPlan.logicalCameraId,
|
|
1345
|
+
null,
|
|
1346
|
+
getFallbackZoomForDeviceId(deviceId),
|
|
1347
|
+
false
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
private CameraBindingPlan buildPositionPlan(String position) {
|
|
1352
|
+
int requiredFacing = "front".equals(position) ? CameraSelector.LENS_FACING_FRONT : CameraSelector.LENS_FACING_BACK;
|
|
1353
|
+
CameraSelector selector = new CameraSelector.Builder().requireLensFacing(requiredFacing).build();
|
|
1354
|
+
return new CameraBindingPlan(selector, null, null, null, 1.0f, false);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
private IllegalArgumentException invalidDeviceId(String deviceId) {
|
|
1358
|
+
return new IllegalArgumentException("Unknown or unsupported deviceId: " + deviceId);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
private CameraInfo findAvailableCameraInfoById(String deviceId) {
|
|
1362
|
+
if (cameraProvider == null || deviceId == null || deviceId.isEmpty()) {
|
|
1363
|
+
return null;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
for (CameraInfo cameraInfo : cameraProvider.getAvailableCameraInfos()) {
|
|
1367
|
+
if (deviceId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())) {
|
|
1368
|
+
return cameraInfo;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
@OptIn(markerClass = ExperimentalCamera2Interop.class)
|
|
1376
|
+
private PhysicalCameraBindingTarget findPhysicalCameraBindingTarget(String physicalDeviceId) {
|
|
1377
|
+
if (
|
|
1378
|
+
cameraProvider == null ||
|
|
1379
|
+
physicalDeviceId == null ||
|
|
1380
|
+
physicalDeviceId.isEmpty() ||
|
|
1381
|
+
Build.VERSION.SDK_INT < Build.VERSION_CODES.P
|
|
1382
|
+
) {
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
|
1387
|
+
if (cameraManager == null) {
|
|
1388
|
+
return null;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
for (CameraInfo cameraInfo : cameraProvider.getAvailableCameraInfos()) {
|
|
1392
|
+
String logicalCameraId = Camera2CameraInfo.from(cameraInfo).getCameraId();
|
|
1393
|
+
try {
|
|
1394
|
+
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(logicalCameraId);
|
|
1395
|
+
if (characteristics.getPhysicalCameraIds().contains(physicalDeviceId)) {
|
|
1396
|
+
int requiredFacing = isBackCamera(cameraInfo) ? CameraSelector.LENS_FACING_BACK : CameraSelector.LENS_FACING_FRONT;
|
|
1397
|
+
return new PhysicalCameraBindingTarget(cameraInfo, logicalCameraId, requiredFacing);
|
|
1398
|
+
}
|
|
1399
|
+
} catch (CameraAccessException e) {
|
|
1400
|
+
Log.w(TAG, "findPhysicalCameraBindingTarget: Failed to inspect logical camera " + logicalCameraId, e);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
return null;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
private PhysicalDeviceMetadata resolvePhysicalDeviceMetadata(String deviceId) {
|
|
1408
|
+
if (deviceId == null || deviceId.isEmpty()) {
|
|
1409
|
+
return null;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
|
|
1413
|
+
if (cameraManager == null) {
|
|
1414
|
+
return null;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
try {
|
|
1418
|
+
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(deviceId);
|
|
1419
|
+
Integer lensFacing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
|
1420
|
+
String position = lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_FRONT ? "front" : "rear";
|
|
1421
|
+
return new PhysicalDeviceMetadata(position, getFallbackZoomForCharacteristics(characteristics));
|
|
1422
|
+
} catch (CameraAccessException | IllegalArgumentException e) {
|
|
1423
|
+
Log.w(TAG, "resolvePhysicalDeviceMetadata: Failed to inspect camera " + deviceId, e);
|
|
1424
|
+
return null;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
private String resolveFallbackPositionForDeviceId(String deviceId) {
|
|
1429
|
+
app.capgo.capacitor.camera.preview.model.CameraDevice device = findEnumeratedDeviceById(deviceId);
|
|
1430
|
+
if (device != null) {
|
|
1431
|
+
return device.getPosition();
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
PhysicalDeviceMetadata metadata = resolvePhysicalDeviceMetadata(deviceId);
|
|
1435
|
+
return metadata != null ? metadata.position : null;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
private app.capgo.capacitor.camera.preview.model.CameraDevice findEnumeratedDeviceById(String deviceId) {
|
|
1439
|
+
if (deviceId == null || deviceId.isEmpty()) {
|
|
1440
|
+
return null;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
app.capgo.capacitor.camera.preview.model.CameraDevice cachedDevice = enumeratedDeviceCache.get(deviceId);
|
|
1444
|
+
if (cachedDevice != null) {
|
|
1445
|
+
return cachedDevice;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
requestEnumeratedDeviceCacheRefresh();
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
private float getFallbackZoomForDeviceId(String deviceId) {
|
|
1453
|
+
app.capgo.capacitor.camera.preview.model.CameraDevice device = findEnumeratedDeviceById(deviceId);
|
|
1454
|
+
if (device != null) {
|
|
1455
|
+
for (LensInfo lens : device.getLenses()) {
|
|
1456
|
+
if ("ultraWide".equals(lens.getDeviceType())) {
|
|
1457
|
+
return 0.5f;
|
|
1458
|
+
}
|
|
1459
|
+
if ("telephoto".equals(lens.getDeviceType())) {
|
|
1460
|
+
return 2.0f;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
PhysicalDeviceMetadata metadata = resolvePhysicalDeviceMetadata(deviceId);
|
|
1466
|
+
if (metadata != null) {
|
|
1467
|
+
return metadata.fallbackZoom;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
return 1.0f;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
private float getFallbackZoomForCharacteristics(CameraCharacteristics characteristics) {
|
|
1474
|
+
float[] focalLengths = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
|
|
1475
|
+
android.util.SizeF sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE);
|
|
1476
|
+
|
|
1477
|
+
if (focalLengths != null && focalLengths.length > 0) {
|
|
1478
|
+
float focalLength = focalLengths[0];
|
|
1479
|
+
if (sensorSize != null && sensorSize.getWidth() > 0) {
|
|
1480
|
+
double fov = 2 * Math.toDegrees(Math.atan(sensorSize.getWidth() / (2 * focalLength)));
|
|
1481
|
+
if (fov > 90) {
|
|
1482
|
+
return 0.5f;
|
|
1483
|
+
}
|
|
1484
|
+
if (fov < 40) {
|
|
1485
|
+
return 2.0f;
|
|
1486
|
+
}
|
|
1487
|
+
} else {
|
|
1488
|
+
if (focalLength < 3.0f) {
|
|
1489
|
+
return 0.5f;
|
|
1490
|
+
}
|
|
1491
|
+
if (focalLength > 5.0f) {
|
|
1492
|
+
return 2.0f;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
return 1.0f;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
private void bindConfiguredUseCases(CameraBindingPlan bindingPlan, Preview preview) {
|
|
1501
|
+
if (sessionConfig.isVideoModeEnabled() && videoCapture != null) {
|
|
1502
|
+
camera = cameraProvider.bindToLifecycle(this, bindingPlan.selector, preview, imageCapture, videoCapture);
|
|
1103
1503
|
} else {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1504
|
+
camera = cameraProvider.bindToLifecycle(this, bindingPlan.selector, preview, imageCapture);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
CameraInfo cameraInfo = camera.getCameraInfo();
|
|
1508
|
+
currentLogicalDeviceId = Camera2CameraInfo.from(cameraInfo).getCameraId();
|
|
1509
|
+
currentPhysicalDeviceId = bindingPlan.physicalCameraId;
|
|
1510
|
+
currentDeviceId = currentPhysicalDeviceId != null ? currentPhysicalDeviceId : currentLogicalDeviceId;
|
|
1511
|
+
|
|
1512
|
+
Log.d(
|
|
1513
|
+
TAG,
|
|
1514
|
+
"bindConfiguredUseCases: Camera successfully bound. activeDeviceId=" +
|
|
1515
|
+
currentDeviceId +
|
|
1516
|
+
", logicalCameraId=" +
|
|
1517
|
+
currentLogicalDeviceId +
|
|
1518
|
+
", physicalCameraId=" +
|
|
1519
|
+
currentPhysicalDeviceId
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
private void copyMutableSessionConfigState(CameraSessionConfiguration source, CameraSessionConfiguration target) {
|
|
1524
|
+
target.setCentered(source.isCentered());
|
|
1525
|
+
target.setTargetZoom(source.getTargetZoom());
|
|
1526
|
+
target.setEnablePhysicalDeviceSelection(source.isPhysicalDeviceSelectionEnabled());
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
private void requestEnumeratedDeviceCacheRefresh() {
|
|
1530
|
+
synchronized (enumeratedDeviceCacheLock) {
|
|
1531
|
+
if (enumeratedDeviceCacheRefreshInProgress) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
enumeratedDeviceCacheRefreshInProgress = true;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
Runnable refreshTask = () -> {
|
|
1538
|
+
try {
|
|
1539
|
+
getAvailableDevicesStatic(context);
|
|
1540
|
+
} finally {
|
|
1541
|
+
synchronized (enumeratedDeviceCacheLock) {
|
|
1542
|
+
enumeratedDeviceCacheRefreshInProgress = false;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
|
|
1547
|
+
if (cameraExecutor != null && !cameraExecutor.isShutdown()) {
|
|
1548
|
+
try {
|
|
1549
|
+
cameraExecutor.execute(refreshTask);
|
|
1550
|
+
return;
|
|
1551
|
+
} catch (RejectedExecutionException e) {
|
|
1552
|
+
Log.w(TAG, "requestEnumeratedDeviceCacheRefresh: cameraExecutor rejected refresh task", e);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
try {
|
|
1557
|
+
Thread refreshThread = new Thread(refreshTask, "CameraPreview-DeviceCacheRefresh");
|
|
1558
|
+
refreshThread.setDaemon(true);
|
|
1559
|
+
refreshThread.start();
|
|
1560
|
+
} catch (RuntimeException e) {
|
|
1561
|
+
synchronized (enumeratedDeviceCacheLock) {
|
|
1562
|
+
enumeratedDeviceCacheRefreshInProgress = false;
|
|
1563
|
+
}
|
|
1564
|
+
throw e;
|
|
1107
1565
|
}
|
|
1108
|
-
return builder.build();
|
|
1109
1566
|
}
|
|
1110
1567
|
|
|
1111
1568
|
private static boolean isBackCamera(androidx.camera.core.CameraInfo cameraInfo) {
|
|
@@ -2191,6 +2648,7 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2191
2648
|
}
|
|
2192
2649
|
|
|
2193
2650
|
Log.d(TAG, "=== Enumeration Complete: " + devices.size() + " cameras ===");
|
|
2651
|
+
updateEnumeratedDeviceCache(devices);
|
|
2194
2652
|
return devices;
|
|
2195
2653
|
} catch (Exception e) {
|
|
2196
2654
|
Log.e(TAG, "getAvailableDevicesStatic: Error getting devices", e);
|
|
@@ -2198,6 +2656,14 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2198
2656
|
}
|
|
2199
2657
|
}
|
|
2200
2658
|
|
|
2659
|
+
private static void updateEnumeratedDeviceCache(List<app.capgo.capacitor.camera.preview.model.CameraDevice> devices) {
|
|
2660
|
+
Map<String, app.capgo.capacitor.camera.preview.model.CameraDevice> newCache = new ConcurrentHashMap<>();
|
|
2661
|
+
for (app.capgo.capacitor.camera.preview.model.CameraDevice device : devices) {
|
|
2662
|
+
newCache.put(device.getDeviceId(), device);
|
|
2663
|
+
}
|
|
2664
|
+
enumeratedDeviceCache = newCache;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2201
2667
|
public static ZoomFactors getZoomFactorsStatic() {
|
|
2202
2668
|
try {
|
|
2203
2669
|
// For static method, return default zoom factors
|
|
@@ -2275,6 +2741,9 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2275
2741
|
}
|
|
2276
2742
|
|
|
2277
2743
|
camera.getCameraControl().setZoomRatio(zoomRatio);
|
|
2744
|
+
if (sessionConfig != null) {
|
|
2745
|
+
sessionConfig.setTargetZoom(zoomRatio);
|
|
2746
|
+
}
|
|
2278
2747
|
// Note: autofocus is intentionally not triggered on zoom because it's done by CameraX
|
|
2279
2748
|
} catch (Exception e) {
|
|
2280
2749
|
Log.e(TAG, "Failed to set zoom: " + e.getMessage());
|
|
@@ -2798,53 +3267,52 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2798
3267
|
|
|
2799
3268
|
mainExecutor.execute(() -> {
|
|
2800
3269
|
try {
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
3270
|
+
CameraSessionConfiguration previousConfig = sessionConfig;
|
|
3271
|
+
CameraInfo targetCameraInfo = findAvailableCameraInfoById(deviceId);
|
|
3272
|
+
String fallbackPosition = resolveFallbackPositionForDeviceId(deviceId);
|
|
3273
|
+
String position = fallbackPosition != null ? fallbackPosition : previousConfig.getPosition();
|
|
3274
|
+
if (targetCameraInfo != null) {
|
|
3275
|
+
position = isBackCamera(targetCameraInfo) ? "rear" : "front";
|
|
3276
|
+
} else if (previousConfig.isPhysicalDeviceSelectionEnabled()) {
|
|
3277
|
+
PhysicalCameraBindingTarget physicalTarget = findPhysicalCameraBindingTarget(deviceId);
|
|
3278
|
+
if (physicalTarget != null) {
|
|
3279
|
+
position = physicalTarget.requiredFacing == CameraSelector.LENS_FACING_FRONT ? "front" : "rear";
|
|
3280
|
+
} else if (fallbackPosition == null) {
|
|
3281
|
+
Log.e(TAG, "switchToDevice: Could not resolve deviceId: " + deviceId);
|
|
3282
|
+
return;
|
|
2810
3283
|
}
|
|
3284
|
+
} else if (fallbackPosition == null) {
|
|
3285
|
+
Log.e(TAG, "switchToDevice: Could not find any CameraInfo matching deviceId: " + deviceId);
|
|
3286
|
+
return;
|
|
2811
3287
|
}
|
|
2812
3288
|
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
sessionConfig.isVideoModeEnabled(),
|
|
2838
|
-
sessionConfig.getVideoQuality()
|
|
2839
|
-
);
|
|
2840
|
-
|
|
2841
|
-
sessionConfig.setCentered(wasCentered);
|
|
3289
|
+
CameraSessionConfiguration updatedConfig = new CameraSessionConfiguration(
|
|
3290
|
+
deviceId,
|
|
3291
|
+
position,
|
|
3292
|
+
previousConfig.getX(),
|
|
3293
|
+
previousConfig.getY(),
|
|
3294
|
+
previousConfig.getWidth(),
|
|
3295
|
+
previousConfig.getHeight(),
|
|
3296
|
+
previousConfig.getPaddingBottom(),
|
|
3297
|
+
previousConfig.getToBack(),
|
|
3298
|
+
previousConfig.getStoreToFile(),
|
|
3299
|
+
previousConfig.getEnableOpacity(),
|
|
3300
|
+
previousConfig.getDisableExifHeaderStripping(),
|
|
3301
|
+
previousConfig.getDisableAudio(),
|
|
3302
|
+
previousConfig.getZoomFactor(),
|
|
3303
|
+
previousConfig.getAspectRatio(),
|
|
3304
|
+
previousConfig.getAspectMode(),
|
|
3305
|
+
previousConfig.getGridMode(),
|
|
3306
|
+
previousConfig.getDisableFocusIndicator(),
|
|
3307
|
+
previousConfig.isVideoModeEnabled(),
|
|
3308
|
+
previousConfig.getVideoQuality()
|
|
3309
|
+
);
|
|
3310
|
+
copyMutableSessionConfigState(previousConfig, updatedConfig);
|
|
3311
|
+
updatedConfig.setTargetZoom(1.0f);
|
|
3312
|
+
sessionConfig = updatedConfig;
|
|
2842
3313
|
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
} else {
|
|
2846
|
-
Log.e(TAG, "switchToDevice: Could not find any CameraInfo matching deviceId: " + deviceId);
|
|
2847
|
-
}
|
|
3314
|
+
Log.d(TAG, "switchToDevice: Updated sessionConfig with deviceId: " + deviceId);
|
|
3315
|
+
bindCameraUseCases();
|
|
2848
3316
|
} catch (Exception e) {
|
|
2849
3317
|
Log.e(TAG, "switchToDevice: Error switching camera", e);
|
|
2850
3318
|
}
|
|
@@ -2862,38 +3330,39 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2862
3330
|
boolean wasCentered = sessionConfig.isCentered();
|
|
2863
3331
|
Log.d(TAG, "flipCamera: Switching from " + currentPosition + " to " + newPosition);
|
|
2864
3332
|
|
|
3333
|
+
CameraSessionConfiguration previousConfig = sessionConfig;
|
|
2865
3334
|
sessionConfig = new CameraSessionConfiguration(
|
|
2866
3335
|
null, // deviceId - clear device ID to force position-based selection
|
|
2867
3336
|
newPosition, // position
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
3337
|
+
previousConfig.getX(), // x
|
|
3338
|
+
previousConfig.getY(), // y
|
|
3339
|
+
previousConfig.getWidth(), // width
|
|
3340
|
+
previousConfig.getHeight(), // height
|
|
3341
|
+
previousConfig.getPaddingBottom(), // paddingBottom
|
|
3342
|
+
previousConfig.isToBack(), // toBack
|
|
3343
|
+
previousConfig.isStoreToFile(), // storeToFile
|
|
3344
|
+
previousConfig.isEnableOpacity(), // enableOpacity
|
|
3345
|
+
previousConfig.isDisableExifHeaderStripping(), // disableExifHeaderStripping
|
|
3346
|
+
previousConfig.isDisableAudio(), // disableAudio
|
|
3347
|
+
previousConfig.getZoomFactor(), // zoomFactor
|
|
3348
|
+
previousConfig.getAspectRatio(), // aspectRatio
|
|
3349
|
+
previousConfig.getAspectMode(), // aspectMode
|
|
3350
|
+
previousConfig.getGridMode(), // gridMode
|
|
3351
|
+
previousConfig.getDisableFocusIndicator(), // disableFocusIndicator
|
|
3352
|
+
previousConfig.isVideoModeEnabled(), // enableVideoMode
|
|
3353
|
+
previousConfig.getVideoQuality() // videoQuality
|
|
2885
3354
|
);
|
|
2886
|
-
|
|
3355
|
+
copyMutableSessionConfigState(previousConfig, sessionConfig);
|
|
3356
|
+
sessionConfig.setTargetZoom(1.0f);
|
|
2887
3357
|
sessionConfig.setCentered(wasCentered);
|
|
2888
3358
|
|
|
2889
|
-
// Clear current device
|
|
3359
|
+
// Clear current device IDs to force position-based selection
|
|
2890
3360
|
currentDeviceId = null;
|
|
3361
|
+
currentPhysicalDeviceId = null;
|
|
3362
|
+
currentLogicalDeviceId = null;
|
|
2891
3363
|
|
|
2892
|
-
//
|
|
2893
|
-
|
|
2894
|
-
currentCameraSelector = buildCameraSelector();
|
|
2895
|
-
bindCameraUseCases();
|
|
2896
|
-
});
|
|
3364
|
+
// Rebind camera with the new position
|
|
3365
|
+
bindCameraUseCases();
|
|
2897
3366
|
}
|
|
2898
3367
|
|
|
2899
3368
|
public void setOpacity(float opacity) {
|
|
@@ -2977,32 +3446,34 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
2977
3446
|
return;
|
|
2978
3447
|
}
|
|
2979
3448
|
|
|
2980
|
-
|
|
3449
|
+
CameraSessionConfiguration previousConfig = sessionConfig;
|
|
3450
|
+
String currentGridMode = previousConfig.getGridMode();
|
|
2981
3451
|
Log.d(TAG, "Changing aspect ratio from " + currentAspectRatio + " to " + aspectRatio);
|
|
2982
3452
|
Log.d(TAG, "Auto-centering will be applied (matching iOS behavior)");
|
|
2983
3453
|
|
|
2984
3454
|
// Match iOS behavior: when aspect ratio changes, always auto-center
|
|
2985
3455
|
sessionConfig = new CameraSessionConfiguration(
|
|
2986
|
-
|
|
2987
|
-
|
|
3456
|
+
previousConfig.getDeviceId(),
|
|
3457
|
+
previousConfig.getPosition(),
|
|
2988
3458
|
-1, // Force auto-center X (iOS: self.posX = -1)
|
|
2989
3459
|
-1, // Force auto-center Y (iOS: self.posY = -1)
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
3460
|
+
previousConfig.getWidth(),
|
|
3461
|
+
previousConfig.getHeight(),
|
|
3462
|
+
previousConfig.getPaddingBottom(),
|
|
3463
|
+
previousConfig.getToBack(),
|
|
3464
|
+
previousConfig.getStoreToFile(),
|
|
3465
|
+
previousConfig.getEnableOpacity(),
|
|
3466
|
+
previousConfig.getDisableExifHeaderStripping(),
|
|
3467
|
+
previousConfig.getDisableAudio(),
|
|
3468
|
+
previousConfig.getZoomFactor(),
|
|
2999
3469
|
aspectRatio,
|
|
3000
|
-
|
|
3470
|
+
previousConfig.getAspectMode(),
|
|
3001
3471
|
currentGridMode,
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3472
|
+
previousConfig.getDisableFocusIndicator(),
|
|
3473
|
+
previousConfig.isVideoModeEnabled(),
|
|
3474
|
+
previousConfig.getVideoQuality()
|
|
3005
3475
|
);
|
|
3476
|
+
copyMutableSessionConfigState(previousConfig, sessionConfig);
|
|
3006
3477
|
sessionConfig.setCentered(true);
|
|
3007
3478
|
|
|
3008
3479
|
// Update layout and rebind camera with new aspect ratio
|
|
@@ -3053,32 +3524,34 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
3053
3524
|
return;
|
|
3054
3525
|
}
|
|
3055
3526
|
|
|
3056
|
-
|
|
3527
|
+
CameraSessionConfiguration previousConfig = sessionConfig;
|
|
3528
|
+
String currentGridMode = previousConfig.getGridMode();
|
|
3057
3529
|
Log.d(TAG, "Forcing aspect ratio recalculation for: " + aspectRatio);
|
|
3058
3530
|
Log.d(TAG, "Auto-centering will be applied (matching iOS behavior)");
|
|
3059
3531
|
|
|
3060
3532
|
// Match iOS behavior: when aspect ratio changes, always auto-center
|
|
3061
3533
|
sessionConfig = new CameraSessionConfiguration(
|
|
3062
|
-
|
|
3063
|
-
|
|
3534
|
+
previousConfig.getDeviceId(),
|
|
3535
|
+
previousConfig.getPosition(),
|
|
3064
3536
|
-1, // Force auto-center X (iOS: self.posX = -1)
|
|
3065
3537
|
-1, // Force auto-center Y (iOS: self.posY = -1)
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3538
|
+
previousConfig.getWidth(),
|
|
3539
|
+
previousConfig.getHeight(),
|
|
3540
|
+
previousConfig.getPaddingBottom(),
|
|
3541
|
+
previousConfig.getToBack(),
|
|
3542
|
+
previousConfig.getStoreToFile(),
|
|
3543
|
+
previousConfig.getEnableOpacity(),
|
|
3544
|
+
previousConfig.getDisableExifHeaderStripping(),
|
|
3545
|
+
previousConfig.getDisableAudio(),
|
|
3546
|
+
previousConfig.getZoomFactor(),
|
|
3075
3547
|
aspectRatio,
|
|
3076
|
-
|
|
3548
|
+
previousConfig.getAspectMode(),
|
|
3077
3549
|
currentGridMode,
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3550
|
+
previousConfig.getDisableFocusIndicator(),
|
|
3551
|
+
previousConfig.isVideoModeEnabled(),
|
|
3552
|
+
previousConfig.getVideoQuality()
|
|
3081
3553
|
);
|
|
3554
|
+
copyMutableSessionConfigState(previousConfig, sessionConfig);
|
|
3082
3555
|
sessionConfig.setCentered(true);
|
|
3083
3556
|
|
|
3084
3557
|
// Update layout and rebind camera with new aspect ratio
|
|
@@ -3121,27 +3594,29 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
3121
3594
|
public void setGridMode(String gridMode) {
|
|
3122
3595
|
if (sessionConfig != null) {
|
|
3123
3596
|
Log.d(TAG, "setGridMode: Changing grid mode to: " + gridMode);
|
|
3597
|
+
CameraSessionConfiguration previousConfig = sessionConfig;
|
|
3124
3598
|
sessionConfig = new CameraSessionConfiguration(
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3599
|
+
previousConfig.getDeviceId(),
|
|
3600
|
+
previousConfig.getPosition(),
|
|
3601
|
+
previousConfig.getX(),
|
|
3602
|
+
previousConfig.getY(),
|
|
3603
|
+
previousConfig.getWidth(),
|
|
3604
|
+
previousConfig.getHeight(),
|
|
3605
|
+
previousConfig.getPaddingBottom(),
|
|
3606
|
+
previousConfig.getToBack(),
|
|
3607
|
+
previousConfig.getStoreToFile(),
|
|
3608
|
+
previousConfig.getEnableOpacity(),
|
|
3609
|
+
previousConfig.getDisableExifHeaderStripping(),
|
|
3610
|
+
previousConfig.getDisableAudio(),
|
|
3611
|
+
previousConfig.getZoomFactor(),
|
|
3612
|
+
previousConfig.getAspectRatio(),
|
|
3613
|
+
previousConfig.getAspectMode(),
|
|
3140
3614
|
gridMode,
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3615
|
+
previousConfig.getDisableFocusIndicator(),
|
|
3616
|
+
previousConfig.isVideoModeEnabled(),
|
|
3617
|
+
previousConfig.getVideoQuality()
|
|
3144
3618
|
);
|
|
3619
|
+
copyMutableSessionConfigState(previousConfig, sessionConfig);
|
|
3145
3620
|
|
|
3146
3621
|
// Update the grid overlay immediately
|
|
3147
3622
|
if (gridOverlayView != null) {
|
|
@@ -3461,27 +3936,29 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
3461
3936
|
);
|
|
3462
3937
|
}
|
|
3463
3938
|
|
|
3939
|
+
CameraSessionConfiguration previousConfig = sessionConfig;
|
|
3464
3940
|
sessionConfig = new CameraSessionConfiguration(
|
|
3465
|
-
|
|
3466
|
-
|
|
3941
|
+
previousConfig.getDeviceId(),
|
|
3942
|
+
previousConfig.getPosition(),
|
|
3467
3943
|
params.leftMargin,
|
|
3468
3944
|
params.topMargin,
|
|
3469
3945
|
params.width,
|
|
3470
3946
|
params.height,
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3947
|
+
previousConfig.getPaddingBottom(),
|
|
3948
|
+
previousConfig.getToBack(),
|
|
3949
|
+
previousConfig.getStoreToFile(),
|
|
3950
|
+
previousConfig.getEnableOpacity(),
|
|
3951
|
+
previousConfig.getDisableExifHeaderStripping(),
|
|
3952
|
+
previousConfig.getDisableAudio(),
|
|
3953
|
+
previousConfig.getZoomFactor(),
|
|
3478
3954
|
calculatedAspectRatio,
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3955
|
+
previousConfig.getAspectMode(),
|
|
3956
|
+
previousConfig.getGridMode(),
|
|
3957
|
+
previousConfig.getDisableFocusIndicator(),
|
|
3958
|
+
previousConfig.isVideoModeEnabled(),
|
|
3959
|
+
previousConfig.getVideoQuality()
|
|
3484
3960
|
);
|
|
3961
|
+
copyMutableSessionConfigState(previousConfig, sessionConfig);
|
|
3485
3962
|
|
|
3486
3963
|
// If aspect ratio changed due to size update, rebind camera
|
|
3487
3964
|
if (isRunning && !Objects.equals(currentAspectRatio, calculatedAspectRatio)) {
|