capacitor-plugin-camera-forked 3.0.93 → 3.0.95
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.
|
@@ -328,9 +328,6 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
328
328
|
|
|
329
329
|
camera.getCameraControl().startFocusAndMetering(initialFocus);
|
|
330
330
|
|
|
331
|
-
// Enable continuous auto-focus by starting a background focus monitoring
|
|
332
|
-
startContinuousAutoFocus();
|
|
333
|
-
|
|
334
331
|
Log.d("Camera", "Initialized responsive auto-focus with continuous monitoring");
|
|
335
332
|
} catch (Exception e) {
|
|
336
333
|
Log.e("Camera", "Failed to initialize responsive auto-focus: " + e.getMessage());
|
|
@@ -338,65 +335,73 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
338
335
|
}
|
|
339
336
|
}
|
|
340
337
|
|
|
341
|
-
/**
|
|
342
|
-
* Start continuous auto-focus monitoring for better focus stability
|
|
343
|
-
*/
|
|
344
|
-
private void startContinuousAutoFocus() {
|
|
345
|
-
if (camera != null && previewView != null) {
|
|
346
|
-
// Use a separate executor for continuous focus to avoid blocking
|
|
347
|
-
ExecutorService focusExecutor = Executors.newSingleThreadExecutor();
|
|
348
|
-
|
|
349
|
-
focusExecutor.execute(new Runnable() {
|
|
350
|
-
@Override
|
|
351
|
-
public void run() {
|
|
352
|
-
try {
|
|
353
|
-
while (camera != null && camera.getCameraInfo().getCameraState().getValue().getType() == CameraState.Type.OPEN) {
|
|
354
|
-
Thread.sleep(800); // Check every 800ms for faster transitions
|
|
355
|
-
|
|
356
|
-
getActivity().runOnUiThread(new Runnable() {
|
|
357
|
-
@Override
|
|
358
|
-
public void run() {
|
|
359
|
-
try {
|
|
360
|
-
// Trigger auto-focus at center to maintain continuous focus
|
|
361
|
-
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
362
|
-
float centerX = previewView.getWidth() / 2.0f;
|
|
363
|
-
float centerY = previewView.getHeight() / 2.0f;
|
|
364
|
-
MeteringPoint centerPoint = factory.createPoint(centerX, centerY);
|
|
365
|
-
|
|
366
|
-
FocusMeteringAction continuousAction = new FocusMeteringAction.Builder(centerPoint)
|
|
367
|
-
.setAutoCancelDuration(1, TimeUnit.SECONDS) // Fast 1 second for responsive transitions
|
|
368
|
-
.build();
|
|
369
|
-
|
|
370
|
-
camera.getCameraControl().startFocusAndMetering(continuousAction);
|
|
371
|
-
} catch (Exception e) {
|
|
372
|
-
Log.d("Camera", "Continuous focus update failed: " + e.getMessage());
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
} catch (InterruptedException e) {
|
|
378
|
-
Log.d("Camera", "Continuous auto-focus stopped");
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
338
|
@PluginMethod
|
|
386
339
|
public void startCamera(PluginCall call) {
|
|
387
340
|
getActivity().runOnUiThread(new Runnable() {
|
|
388
341
|
public void run() {
|
|
389
342
|
try {
|
|
390
|
-
|
|
343
|
+
// Validate that all required components are ready
|
|
344
|
+
if (cameraProvider == null) {
|
|
345
|
+
call.reject("Camera provider not initialized");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (cameraSelector == null) {
|
|
349
|
+
call.reject("Camera selector not initialized");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (useCaseGroup == null) {
|
|
353
|
+
// Re-initialize use cases if they were cleared
|
|
354
|
+
setupUseCases(false);
|
|
355
|
+
if (useCaseGroup == null) {
|
|
356
|
+
call.reject("Camera use cases not initialized");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (previewView == null) {
|
|
361
|
+
call.reject("Preview view not initialized");
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Ensure preview surface is properly connected
|
|
366
|
+
if (preview != null) {
|
|
367
|
+
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Make UI changes first
|
|
391
371
|
previewView.setVisibility(View.VISIBLE);
|
|
392
372
|
previewView.setBackgroundColor(Color.BLACK);
|
|
393
373
|
makeWebViewTransparent();
|
|
394
|
-
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
374
|
+
|
|
375
|
+
// Small delay to ensure preview surface is ready
|
|
376
|
+
exec.execute(new Runnable() {
|
|
377
|
+
@Override
|
|
378
|
+
public void run() {
|
|
379
|
+
try {
|
|
380
|
+
Thread.sleep(50); // Brief delay for surface initialization
|
|
381
|
+
|
|
382
|
+
getActivity().runOnUiThread(new Runnable() {
|
|
383
|
+
@Override
|
|
384
|
+
public void run() {
|
|
385
|
+
try {
|
|
386
|
+
// Bind camera to lifecycle
|
|
387
|
+
camera = cameraProvider.bindToLifecycle((LifecycleOwner) getContext(), cameraSelector, useCaseGroup);
|
|
388
|
+
|
|
389
|
+
// Initialize responsive auto-focus for better performance
|
|
390
|
+
initializeResponsiveAutoFocus();
|
|
391
|
+
|
|
392
|
+
triggerOnPlayed();
|
|
393
|
+
call.resolve();
|
|
394
|
+
} catch (Exception e) {
|
|
395
|
+
e.printStackTrace();
|
|
396
|
+
call.reject("Failed to bind camera: " + e.getMessage());
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
} catch (InterruptedException e) {
|
|
401
|
+
call.reject("Camera start interrupted: " + e.getMessage());
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
});
|
|
400
405
|
} catch (Exception e) {
|
|
401
406
|
e.printStackTrace();
|
|
402
407
|
call.reject(e.getMessage());
|
|
@@ -411,9 +416,22 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
411
416
|
public void run() {
|
|
412
417
|
try {
|
|
413
418
|
restoreWebViewBackground();
|
|
414
|
-
previewView
|
|
415
|
-
|
|
416
|
-
|
|
419
|
+
if (previewView != null) {
|
|
420
|
+
previewView.setVisibility(View.INVISIBLE);
|
|
421
|
+
previewView.setBackgroundColor(Color.BLACK);
|
|
422
|
+
}
|
|
423
|
+
if (cameraProvider != null) {
|
|
424
|
+
cameraProvider.unbindAll();
|
|
425
|
+
}
|
|
426
|
+
// Null out references to help GC and ensure release
|
|
427
|
+
camera = null;
|
|
428
|
+
imageCapture = null;
|
|
429
|
+
preview = null;
|
|
430
|
+
imageAnalysis = null;
|
|
431
|
+
useCaseGroup = null;
|
|
432
|
+
recorder = null;
|
|
433
|
+
currentRecording = null;
|
|
434
|
+
Log.d("Camera", "Camera stopped and all references cleared.");
|
|
417
435
|
call.resolve();
|
|
418
436
|
} catch (Exception e) {
|
|
419
437
|
call.reject(e.getMessage());
|
|
@@ -508,24 +526,8 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
508
526
|
.setAutoCancelDuration(1, TimeUnit.SECONDS) // Fast 1 second for zoom focus
|
|
509
527
|
.build();
|
|
510
528
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
zoomFocusFuture.addListener(new Runnable() {
|
|
515
|
-
@Override
|
|
516
|
-
public void run() {
|
|
517
|
-
try {
|
|
518
|
-
FocusMeteringResult result = zoomFocusFuture.get();
|
|
519
|
-
if (result.isFocusSuccessful()) {
|
|
520
|
-
Log.d("Camera", "Zoom-triggered focus successful for factor: " + factor);
|
|
521
|
-
} else {
|
|
522
|
-
Log.d("Camera", "Zoom-triggered focus failed, will rely on continuous AF");
|
|
523
|
-
}
|
|
524
|
-
} catch (Exception e) {
|
|
525
|
-
Log.d("Camera", "Zoom focus result check failed: " + e.getMessage());
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}, ContextCompat.getMainExecutor(getContext()));
|
|
529
|
+
// Trigger focus after zoom change - simplified without result handling
|
|
530
|
+
camera.getCameraControl().startFocusAndMetering(zoomFocusAction);
|
|
529
531
|
}
|
|
530
532
|
} catch (Exception e) {
|
|
531
533
|
Log.d("Camera", "Auto-focus after zoom failed: " + e.getMessage());
|
|
@@ -609,39 +611,24 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
609
611
|
// Start focus and metering with result callback
|
|
610
612
|
ListenableFuture<FocusMeteringResult> future = camera.getCameraControl().startFocusAndMetering(action);
|
|
611
613
|
|
|
612
|
-
//
|
|
614
|
+
// Simple callback to return focus result
|
|
613
615
|
future.addListener(new Runnable() {
|
|
614
616
|
@Override
|
|
615
617
|
public void run() {
|
|
616
|
-
try {
|
|
617
|
-
FocusMeteringResult result = future.get();
|
|
618
618
|
JSObject response = new JSObject();
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
// If focus failed, try a backup focus attempt to reduce need for multiple taps
|
|
632
|
-
if (!result.isFocusSuccessful()) {
|
|
633
|
-
Log.d("Camera", "Initial focus failed, attempting backup focus");
|
|
634
|
-
performBackupFocus(previewX, previewY);
|
|
635
|
-
} else {
|
|
636
|
-
// If manual focus was successful, maintain it with a follow-up action
|
|
637
|
-
maintainFocusAtPoint(previewX, previewY);
|
|
619
|
+
try {
|
|
620
|
+
FocusMeteringResult result = future.get();
|
|
621
|
+
response.put("success", true);
|
|
622
|
+
response.put("autoFocusSuccessful", result.isFocusSuccessful());
|
|
623
|
+
response.put("x", x);
|
|
624
|
+
response.put("y", y);
|
|
625
|
+
|
|
626
|
+
call.resolve(response);
|
|
627
|
+
} catch (Exception e) {
|
|
628
|
+
response.put("success", false);
|
|
629
|
+
Log.e("Camera", "Focus operation failed", e);
|
|
630
|
+
call.resolve(response);
|
|
638
631
|
}
|
|
639
|
-
|
|
640
|
-
call.resolve(response);
|
|
641
|
-
} catch (Exception e) {
|
|
642
|
-
Log.e("Camera", "Focus operation failed", e);
|
|
643
|
-
call.reject("Focus operation failed: " + e.getMessage());
|
|
644
|
-
}
|
|
645
632
|
}
|
|
646
633
|
}, ContextCompat.getMainExecutor(getContext()));
|
|
647
634
|
|
|
@@ -653,112 +640,6 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
653
640
|
});
|
|
654
641
|
}
|
|
655
642
|
|
|
656
|
-
/**
|
|
657
|
-
* Perform backup focus attempt if initial focus fails
|
|
658
|
-
* This reduces the need for users to tap multiple times
|
|
659
|
-
*/
|
|
660
|
-
private void performBackupFocus(float previewX, float previewY) {
|
|
661
|
-
if (camera == null || previewView == null) return;
|
|
662
|
-
|
|
663
|
-
// Wait a moment for the camera to settle
|
|
664
|
-
ExecutorService backupFocusExecutor = Executors.newSingleThreadExecutor();
|
|
665
|
-
backupFocusExecutor.execute(new Runnable() {
|
|
666
|
-
@Override
|
|
667
|
-
public void run() {
|
|
668
|
-
try {
|
|
669
|
-
Thread.sleep(200); // Wait 200ms for faster backup focus
|
|
670
|
-
|
|
671
|
-
getActivity().runOnUiThread(new Runnable() {
|
|
672
|
-
@Override
|
|
673
|
-
public void run() {
|
|
674
|
-
try {
|
|
675
|
-
if (camera != null && previewView != null) {
|
|
676
|
-
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
677
|
-
MeteringPoint backupPoint = factory.createPoint(previewX, previewY);
|
|
678
|
-
|
|
679
|
-
// Try with fast duration for responsive backup focus
|
|
680
|
-
FocusMeteringAction backupAction = new FocusMeteringAction.Builder(backupPoint,
|
|
681
|
-
FocusMeteringAction.FLAG_AF | FocusMeteringAction.FLAG_AE)
|
|
682
|
-
.setAutoCancelDuration(1, TimeUnit.SECONDS) // Fast 1 second for backup
|
|
683
|
-
.build();
|
|
684
|
-
|
|
685
|
-
ListenableFuture<FocusMeteringResult> backupFuture =
|
|
686
|
-
camera.getCameraControl().startFocusAndMetering(backupAction);
|
|
687
|
-
|
|
688
|
-
backupFuture.addListener(new Runnable() {
|
|
689
|
-
@Override
|
|
690
|
-
public void run() {
|
|
691
|
-
try {
|
|
692
|
-
FocusMeteringResult backupResult = backupFuture.get();
|
|
693
|
-
if (backupResult.isFocusSuccessful()) {
|
|
694
|
-
Log.d("Camera", "Backup focus successful");
|
|
695
|
-
maintainFocusAtPoint(previewX, previewY);
|
|
696
|
-
} else {
|
|
697
|
-
Log.d("Camera", "Backup focus also failed");
|
|
698
|
-
}
|
|
699
|
-
} catch (Exception e) {
|
|
700
|
-
Log.d("Camera", "Backup focus exception: " + e.getMessage());
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}, ContextCompat.getMainExecutor(getContext()));
|
|
704
|
-
}
|
|
705
|
-
} catch (Exception e) {
|
|
706
|
-
Log.d("Camera", "Backup focus setup failed: " + e.getMessage());
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
});
|
|
710
|
-
} catch (InterruptedException e) {
|
|
711
|
-
Log.d("Camera", "Backup focus interrupted");
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Maintain focus at a specific point with repeated focus actions for stability
|
|
719
|
-
*/
|
|
720
|
-
private void maintainFocusAtPoint(float previewX, float previewY) {
|
|
721
|
-
if (camera == null || previewView == null) return;
|
|
722
|
-
|
|
723
|
-
// Use a separate executor for focus maintenance
|
|
724
|
-
ExecutorService focusMaintainExecutor = Executors.newSingleThreadExecutor();
|
|
725
|
-
|
|
726
|
-
focusMaintainExecutor.execute(new Runnable() {
|
|
727
|
-
@Override
|
|
728
|
-
public void run() {
|
|
729
|
-
try {
|
|
730
|
-
// Maintain focus for 2 seconds with quick refocus for responsive transitions
|
|
731
|
-
for (int i = 0; i < 2; i++) {
|
|
732
|
-
Thread.sleep(1000); // Wait 1 second between focus actions
|
|
733
|
-
|
|
734
|
-
getActivity().runOnUiThread(new Runnable() {
|
|
735
|
-
@Override
|
|
736
|
-
public void run() {
|
|
737
|
-
try {
|
|
738
|
-
if (camera != null && camera.getCameraInfo().getCameraState().getValue().getType() == CameraState.Type.OPEN) {
|
|
739
|
-
MeteringPointFactory factory = previewView.getMeteringPointFactory();
|
|
740
|
-
MeteringPoint maintainPoint = factory.createPoint(previewX, previewY);
|
|
741
|
-
|
|
742
|
-
FocusMeteringAction maintainAction = new FocusMeteringAction.Builder(maintainPoint)
|
|
743
|
-
.setAutoCancelDuration(1, TimeUnit.SECONDS) // Fast 1 second maintenance
|
|
744
|
-
.build();
|
|
745
|
-
|
|
746
|
-
camera.getCameraControl().startFocusAndMetering(maintainAction);
|
|
747
|
-
Log.d("Camera", "Maintaining focus at tapped point");
|
|
748
|
-
}
|
|
749
|
-
} catch (Exception e) {
|
|
750
|
-
Log.d("Camera", "Focus maintenance failed: " + e.getMessage());
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
} catch (InterruptedException e) {
|
|
756
|
-
Log.d("Camera", "Focus maintenance interrupted");
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
|
|
762
643
|
@PluginMethod
|
|
763
644
|
public void setAutoFocusMode(PluginCall call) {
|
|
764
645
|
if (camera == null) {
|
|
@@ -928,9 +809,7 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
928
809
|
.build();
|
|
929
810
|
|
|
930
811
|
camera.getCameraControl().startFocusAndMetering(restartAction);
|
|
931
|
-
|
|
932
|
-
// Restart the continuous auto-focus monitoring
|
|
933
|
-
startContinuousAutoFocus();
|
|
812
|
+
|
|
934
813
|
}
|
|
935
814
|
|
|
936
815
|
JSObject result = new JSObject();
|
|
@@ -1360,12 +1239,21 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
1360
1239
|
|
|
1361
1240
|
@Override
|
|
1362
1241
|
protected void handleOnPause() {
|
|
1363
|
-
if (camera != null) {
|
|
1242
|
+
if (camera != null && cameraProvider != null) {
|
|
1364
1243
|
CameraState cameraStatus = camera.getCameraInfo().getCameraState().getValue();
|
|
1365
1244
|
previousCameraStatus = cameraStatus;
|
|
1366
1245
|
if (cameraStatus.getType() == CameraState.Type.OPEN) {
|
|
1367
1246
|
cameraProvider.unbindAll();
|
|
1368
1247
|
}
|
|
1248
|
+
// Null out references
|
|
1249
|
+
camera = null;
|
|
1250
|
+
imageCapture = null;
|
|
1251
|
+
preview = null;
|
|
1252
|
+
imageAnalysis = null;
|
|
1253
|
+
useCaseGroup = null;
|
|
1254
|
+
recorder = null;
|
|
1255
|
+
currentRecording = null;
|
|
1256
|
+
Log.d("Camera", "handleOnPause: Camera stopped and references cleared.");
|
|
1369
1257
|
}
|
|
1370
1258
|
super.handleOnPause();
|
|
1371
1259
|
}
|
|
@@ -1444,4 +1332,4 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
1444
1332
|
call.resolve(result);
|
|
1445
1333
|
}
|
|
1446
1334
|
|
|
1447
|
-
}
|
|
1335
|
+
}
|
|
@@ -33,6 +33,9 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
33
33
|
private let focusThrottleInterval: TimeInterval = 0.5
|
|
34
34
|
var currentCameraDevice: AVCaptureDevice?
|
|
35
35
|
|
|
36
|
+
// Simple focus management with fixed delay
|
|
37
|
+
var continuousFocusReturnTimer: Timer?
|
|
38
|
+
|
|
36
39
|
// Store the desired JPEG quality, set during initialization
|
|
37
40
|
var desiredJpegQuality: CGFloat = 0.95 // Default to high quality (0.0-1.0)
|
|
38
41
|
|
|
@@ -874,51 +877,17 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
874
877
|
x >= 0.0 && x <= 1.0, y >= 0.0 && y <= 1.0 {
|
|
875
878
|
let point = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
|
876
879
|
|
|
877
|
-
//
|
|
878
|
-
if isFocusAnimating {
|
|
879
|
-
resetFocusIfStuck()
|
|
880
|
-
}
|
|
881
|
-
|
|
880
|
+
// Simplified focus without complex throttling or maintenance
|
|
882
881
|
focusWithPoint(point: point)
|
|
883
882
|
|
|
884
|
-
// Calculate the point in the preview layer's coordinate space
|
|
885
|
-
let previewPoint = CGPoint(x: point.x * previewView.bounds.width,
|
|
886
|
-
y: point.y * previewView.bounds.height)
|
|
887
|
-
// showFocusView(at: previewPoint)
|
|
888
883
|
call.resolve()
|
|
889
884
|
} else {
|
|
890
885
|
call.reject("Invalid coordinates. Provide normalized x,y values (0.0-1.0)")
|
|
891
886
|
}
|
|
892
887
|
}
|
|
893
888
|
|
|
894
|
-
private func resetFocusIfStuck() {
|
|
895
|
-
DispatchQueue.main.async { [weak self] in
|
|
896
|
-
guard let self = self else { return }
|
|
897
|
-
|
|
898
|
-
// Remove any existing focus indicator
|
|
899
|
-
self.focusView?.removeFromSuperview()
|
|
900
|
-
self.focusCompletionTimer?.invalidate()
|
|
901
|
-
self.isFocusAnimating = false
|
|
902
|
-
|
|
903
|
-
// Reset focus to continuous mode
|
|
904
|
-
guard let videoInput = self.videoInput else { return }
|
|
905
|
-
let device = videoInput.device
|
|
906
|
-
do {
|
|
907
|
-
try device.lockForConfiguration()
|
|
908
|
-
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
909
|
-
device.focusMode = .continuousAutoFocus
|
|
910
|
-
}
|
|
911
|
-
device.unlockForConfiguration()
|
|
912
|
-
} catch {
|
|
913
|
-
print("Could not reset focus: \(error)")
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
889
|
@objc func resetFocus(_ call: CAPPluginCall) {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
// Reset to center focus
|
|
890
|
+
// Simple reset to center focus
|
|
922
891
|
let centerPoint = CGPoint(x: 0.5, y: 0.5)
|
|
923
892
|
focusWithPoint(point: centerPoint)
|
|
924
893
|
|
|
@@ -930,72 +899,40 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
930
899
|
let convertedPoint = self.previewView.videoPreviewLayer.captureDevicePointConverted(fromLayerPoint: location)
|
|
931
900
|
|
|
932
901
|
focusWithPoint(point: convertedPoint)
|
|
933
|
-
// showFocusView(at: location)
|
|
934
902
|
}
|
|
935
903
|
|
|
936
904
|
func focusWithPoint(point: CGPoint) {
|
|
937
905
|
guard let videoInput = self.videoInput else { return }
|
|
938
906
|
let device = videoInput.device
|
|
939
907
|
|
|
940
|
-
let now = Date()
|
|
941
|
-
if now.timeIntervalSince(lastFocusTime) < focusThrottleInterval {
|
|
942
|
-
return
|
|
943
|
-
}
|
|
944
|
-
lastFocusTime = now
|
|
945
|
-
|
|
946
908
|
do {
|
|
947
909
|
try device.lockForConfiguration()
|
|
948
910
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
if device.isFocusPointOfInterestSupported {
|
|
911
|
+
// Cancel any existing focus/exposure operations
|
|
912
|
+
if device.isFocusModeSupported(.autoFocus) && device.isFocusPointOfInterestSupported {
|
|
952
913
|
device.focusPointOfInterest = point
|
|
953
|
-
|
|
954
|
-
// Use autoFocus for more aggressive focusing on specific points
|
|
955
|
-
if device.isFocusModeSupported(.autoFocus) {
|
|
956
|
-
device.focusMode = .autoFocus
|
|
957
|
-
|
|
958
|
-
// Set up observer for focus completion
|
|
959
|
-
NotificationCenter.default.addObserver(
|
|
960
|
-
self,
|
|
961
|
-
selector: #selector(subjectAreaDidChange),
|
|
962
|
-
name: .AVCaptureDeviceSubjectAreaDidChange,
|
|
963
|
-
object: device
|
|
964
|
-
)
|
|
965
|
-
} else if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
966
|
-
device.focusMode = .continuousAutoFocus
|
|
967
|
-
}
|
|
914
|
+
device.focusMode = .autoFocus
|
|
968
915
|
}
|
|
969
916
|
|
|
970
|
-
if device.isExposurePointOfInterestSupported {
|
|
917
|
+
if device.isExposureModeSupported(.autoExpose) && device.isExposurePointOfInterestSupported {
|
|
971
918
|
device.exposurePointOfInterest = point
|
|
972
|
-
|
|
973
|
-
// Use autoExpose for specific point exposure
|
|
974
|
-
if device.isExposureModeSupported(.autoExpose) {
|
|
975
|
-
device.exposureMode = .autoExpose
|
|
976
|
-
} else if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
977
|
-
device.exposureMode = .continuousAutoExposure
|
|
978
|
-
}
|
|
919
|
+
device.exposureMode = .autoExpose
|
|
979
920
|
}
|
|
980
921
|
|
|
981
|
-
// Ensure full focus range is available
|
|
922
|
+
// Ensure full focus range is available
|
|
982
923
|
if device.isAutoFocusRangeRestrictionSupported {
|
|
983
924
|
device.autoFocusRangeRestriction = .none
|
|
984
925
|
}
|
|
985
926
|
|
|
986
|
-
device.isSubjectAreaChangeMonitoringEnabled = true
|
|
987
|
-
|
|
988
927
|
device.unlockForConfiguration()
|
|
989
928
|
|
|
990
|
-
//
|
|
991
|
-
|
|
992
|
-
self?.returnToContinuousFocus()
|
|
993
|
-
}
|
|
929
|
+
// Cancel any existing timer
|
|
930
|
+
continuousFocusReturnTimer?.invalidate()
|
|
994
931
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
932
|
+
// Return to continuous focus after fixed delay
|
|
933
|
+
// This provides predictable behavior - user has 2 seconds to take photo after tap-to-focus
|
|
934
|
+
continuousFocusReturnTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in
|
|
935
|
+
self?.returnToContinuousFocus()
|
|
999
936
|
}
|
|
1000
937
|
|
|
1001
938
|
} catch {
|
|
@@ -1003,26 +940,16 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
1003
940
|
}
|
|
1004
941
|
}
|
|
1005
942
|
|
|
1006
|
-
@objc private func subjectAreaDidChange(notification: NSNotification) {
|
|
1007
|
-
DispatchQueue.main.async { [weak self] in
|
|
1008
|
-
self?.hideFocusIndicatorWithCompletion()
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
NotificationCenter.default.removeObserver(self, name: .AVCaptureDeviceSubjectAreaDidChange, object: notification.object)
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
943
|
private func returnToContinuousFocus() {
|
|
1015
944
|
guard let videoInput = self.videoInput else { return }
|
|
1016
945
|
let device = videoInput.device
|
|
1017
946
|
do {
|
|
1018
947
|
try device.lockForConfiguration()
|
|
1019
948
|
|
|
1020
|
-
// Return to continuous auto focus for automatic operation
|
|
1021
949
|
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
1022
950
|
device.focusMode = .continuousAutoFocus
|
|
1023
951
|
}
|
|
1024
952
|
|
|
1025
|
-
// Return to continuous auto exposure
|
|
1026
953
|
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
1027
954
|
device.exposureMode = .continuousAutoExposure
|
|
1028
955
|
}
|
|
@@ -1033,58 +960,6 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
1033
960
|
}
|
|
1034
961
|
}
|
|
1035
962
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
private func hideFocusIndicatorWithCompletion() {
|
|
1039
|
-
DispatchQueue.main.async { [weak self] in
|
|
1040
|
-
guard let self = self, let focusView = self.focusView else { return }
|
|
1041
|
-
|
|
1042
|
-
UIView.animate(withDuration: 0.2, animations: {
|
|
1043
|
-
focusView.alpha = 0.0
|
|
1044
|
-
focusView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
|
|
1045
|
-
}) { _ in
|
|
1046
|
-
focusView.removeFromSuperview()
|
|
1047
|
-
focusView.transform = CGAffineTransform.identity
|
|
1048
|
-
self.isFocusAnimating = false
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// func showFocusView(at point: CGPoint) {
|
|
1054
|
-
// DispatchQueue.main.async { [weak self] in
|
|
1055
|
-
// guard let self = self else { return }
|
|
1056
|
-
|
|
1057
|
-
// if self.isFocusAnimating {
|
|
1058
|
-
// self.focusView?.removeFromSuperview()
|
|
1059
|
-
// self.focusCompletionTimer?.invalidate()
|
|
1060
|
-
// }
|
|
1061
|
-
|
|
1062
|
-
// // Create focus view if needed - but make it invisible
|
|
1063
|
-
// if self.focusView == nil {
|
|
1064
|
-
// self.focusView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
|
|
1065
|
-
// // Make the focus view completely transparent
|
|
1066
|
-
// self.focusView?.layer.borderColor = UIColor.clear.cgColor
|
|
1067
|
-
// self.focusView?.layer.borderWidth = 0.0
|
|
1068
|
-
// self.focusView?.layer.cornerRadius = 40
|
|
1069
|
-
// self.focusView?.backgroundColor = .clear
|
|
1070
|
-
// self.focusView?.alpha = 0.0
|
|
1071
|
-
|
|
1072
|
-
// // Remove the inner circle to make it completely invisible
|
|
1073
|
-
// // No inner circle added
|
|
1074
|
-
// }
|
|
1075
|
-
|
|
1076
|
-
// self.focusView?.center = point
|
|
1077
|
-
// self.focusView?.alpha = 0.0 // Keep invisible
|
|
1078
|
-
// self.focusView?.transform = CGAffineTransform.identity
|
|
1079
|
-
// self.previewView.addSubview(self.focusView!)
|
|
1080
|
-
|
|
1081
|
-
// self.isFocusAnimating = true
|
|
1082
|
-
|
|
1083
|
-
// // Skip the animation since the view is invisible
|
|
1084
|
-
// // Focus functionality still works, just no visual feedback
|
|
1085
|
-
// }
|
|
1086
|
-
// }
|
|
1087
|
-
|
|
1088
963
|
@objc func requestCameraPermission(_ call: CAPPluginCall) {
|
|
1089
964
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
1090
965
|
DispatchQueue.main.async {
|
|
@@ -1200,14 +1075,59 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
1200
1075
|
}
|
|
1201
1076
|
|
|
1202
1077
|
@objc func takePhoto(_ call: CAPPluginCall) {
|
|
1203
|
-
call.keepAlive = true
|
|
1204
1078
|
takePhotoCall = call
|
|
1205
|
-
|
|
1079
|
+
|
|
1080
|
+
// Configure photo settings
|
|
1081
|
+
let photoSettings = AVCapturePhotoSettings()
|
|
1082
|
+
photoSettings.isHighResolutionPhotoEnabled = true
|
|
1083
|
+
|
|
1084
|
+
// Use JPEG format with quality setting
|
|
1085
|
+
if #available(iOS 11.0, *) {
|
|
1086
|
+
photoSettings.photoQualityPrioritization = .quality
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Configure for optimal photo capture without complex focus management
|
|
1090
|
+
guard let device = videoInput?.device else {
|
|
1091
|
+
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
1092
|
+
return
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
if device.isFocusModeSupported(.autoFocus) {
|
|
1096
|
+
do {
|
|
1097
|
+
try device.lockForConfiguration()
|
|
1098
|
+
|
|
1099
|
+
// Set to auto focus for capture
|
|
1100
|
+
device.focusMode = .autoFocus
|
|
1101
|
+
|
|
1102
|
+
// Ensure full focus range
|
|
1103
|
+
if device.isAutoFocusRangeRestrictionSupported {
|
|
1104
|
+
device.autoFocusRangeRestriction = .none
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// Set exposure for photo
|
|
1108
|
+
if device.isExposureModeSupported(.autoExpose) {
|
|
1109
|
+
device.exposureMode = .autoExpose
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
device.unlockForConfiguration()
|
|
1113
|
+
|
|
1114
|
+
// Wait briefly for focus, then capture
|
|
1115
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
1116
|
+
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
1117
|
+
}
|
|
1118
|
+
} catch {
|
|
1119
|
+
print("Could not configure focus for capture: \(error)")
|
|
1120
|
+
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
1121
|
+
}
|
|
1122
|
+
} else {
|
|
1123
|
+
// Capture immediately if auto focus isn't supported
|
|
1124
|
+
self.photoOutput.capturePhoto(with: photoSettings, delegate: self)
|
|
1125
|
+
}
|
|
1206
1126
|
}
|
|
1207
1127
|
|
|
1208
1128
|
deinit {
|
|
1209
|
-
NotificationCenter.default.removeObserver(self)
|
|
1210
1129
|
focusCompletionTimer?.invalidate()
|
|
1130
|
+
continuousFocusReturnTimer?.invalidate()
|
|
1211
1131
|
}
|
|
1212
1132
|
|
|
1213
1133
|
}
|
package/package.json
CHANGED