capacitor-plugin-camera-forked 3.0.93 → 3.0.94
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,69 @@ 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
|
+
call.reject("Camera use cases not initialized");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (previewView == null) {
|
|
357
|
+
call.reject("Preview view not initialized");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Ensure preview surface is properly connected
|
|
362
|
+
if (preview != null) {
|
|
363
|
+
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Make UI changes first
|
|
391
367
|
previewView.setVisibility(View.VISIBLE);
|
|
392
368
|
previewView.setBackgroundColor(Color.BLACK);
|
|
393
369
|
makeWebViewTransparent();
|
|
394
|
-
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
370
|
+
|
|
371
|
+
// Small delay to ensure preview surface is ready
|
|
372
|
+
exec.execute(new Runnable() {
|
|
373
|
+
@Override
|
|
374
|
+
public void run() {
|
|
375
|
+
try {
|
|
376
|
+
Thread.sleep(50); // Brief delay for surface initialization
|
|
377
|
+
|
|
378
|
+
getActivity().runOnUiThread(new Runnable() {
|
|
379
|
+
@Override
|
|
380
|
+
public void run() {
|
|
381
|
+
try {
|
|
382
|
+
// Bind camera to lifecycle
|
|
383
|
+
camera = cameraProvider.bindToLifecycle((LifecycleOwner) getContext(), cameraSelector, useCaseGroup);
|
|
384
|
+
|
|
385
|
+
// Initialize responsive auto-focus for better performance
|
|
386
|
+
initializeResponsiveAutoFocus();
|
|
387
|
+
|
|
388
|
+
triggerOnPlayed();
|
|
389
|
+
call.resolve();
|
|
390
|
+
} catch (Exception e) {
|
|
391
|
+
e.printStackTrace();
|
|
392
|
+
call.reject("Failed to bind camera: " + e.getMessage());
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
} catch (InterruptedException e) {
|
|
397
|
+
call.reject("Camera start interrupted: " + e.getMessage());
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
400
401
|
} catch (Exception e) {
|
|
401
402
|
e.printStackTrace();
|
|
402
403
|
call.reject(e.getMessage());
|
|
@@ -508,24 +509,8 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
508
509
|
.setAutoCancelDuration(1, TimeUnit.SECONDS) // Fast 1 second for zoom focus
|
|
509
510
|
.build();
|
|
510
511
|
|
|
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()));
|
|
512
|
+
// Trigger focus after zoom change - simplified without result handling
|
|
513
|
+
camera.getCameraControl().startFocusAndMetering(zoomFocusAction);
|
|
529
514
|
}
|
|
530
515
|
} catch (Exception e) {
|
|
531
516
|
Log.d("Camera", "Auto-focus after zoom failed: " + e.getMessage());
|
|
@@ -609,39 +594,24 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
609
594
|
// Start focus and metering with result callback
|
|
610
595
|
ListenableFuture<FocusMeteringResult> future = camera.getCameraControl().startFocusAndMetering(action);
|
|
611
596
|
|
|
612
|
-
//
|
|
597
|
+
// Simple callback to return focus result
|
|
613
598
|
future.addListener(new Runnable() {
|
|
614
599
|
@Override
|
|
615
600
|
public void run() {
|
|
616
|
-
try {
|
|
617
|
-
FocusMeteringResult result = future.get();
|
|
618
601
|
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);
|
|
602
|
+
try {
|
|
603
|
+
FocusMeteringResult result = future.get();
|
|
604
|
+
response.put("success", true);
|
|
605
|
+
response.put("autoFocusSuccessful", result.isFocusSuccessful());
|
|
606
|
+
response.put("x", x);
|
|
607
|
+
response.put("y", y);
|
|
608
|
+
|
|
609
|
+
call.resolve(response);
|
|
610
|
+
} catch (Exception e) {
|
|
611
|
+
response.put("success", false);
|
|
612
|
+
Log.e("Camera", "Focus operation failed", e);
|
|
613
|
+
call.resolve(response);
|
|
638
614
|
}
|
|
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
615
|
}
|
|
646
616
|
}, ContextCompat.getMainExecutor(getContext()));
|
|
647
617
|
|
|
@@ -653,112 +623,6 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
653
623
|
});
|
|
654
624
|
}
|
|
655
625
|
|
|
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
626
|
@PluginMethod
|
|
763
627
|
public void setAutoFocusMode(PluginCall call) {
|
|
764
628
|
if (camera == null) {
|
|
@@ -928,9 +792,7 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
928
792
|
.build();
|
|
929
793
|
|
|
930
794
|
camera.getCameraControl().startFocusAndMetering(restartAction);
|
|
931
|
-
|
|
932
|
-
// Restart the continuous auto-focus monitoring
|
|
933
|
-
startContinuousAutoFocus();
|
|
795
|
+
|
|
934
796
|
}
|
|
935
797
|
|
|
936
798
|
JSObject result = new JSObject();
|
|
@@ -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