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
- camera = cameraProvider.bindToLifecycle((LifecycleOwner) getContext(), cameraSelector, useCaseGroup);
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
- // Initialize responsive auto-focus for better performance
396
- initializeResponsiveAutoFocus();
397
-
398
- triggerOnPlayed();
399
- call.resolve();
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.setVisibility(View.INVISIBLE);
415
- previewView.setBackgroundColor(Color.BLACK);
416
- cameraProvider.unbindAll();
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
- ListenableFuture<FocusMeteringResult> zoomFocusFuture =
512
- camera.getCameraControl().startFocusAndMetering(zoomFocusAction);
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
- // Add callback to handle the result with enhanced focus maintenance
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
- response.put("success", true);
620
- response.put("autoFocusSuccessful", result.isFocusSuccessful());
621
-
622
- if (includeExposure) {
623
- // Note: CameraX FocusMeteringResult doesn't provide separate exposure success status
624
- // We'll indicate that exposure was attempted along with focus
625
- response.put("autoExposureSuccessful", result.isFocusSuccessful());
626
- }
627
-
628
- response.put("x", x);
629
- response.put("y", y);
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
- // Check if focus is currently animating and reset if stuck
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
- resetFocusIfStuck()
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
- focusCompletionTimer?.invalidate()
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 for close objects
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
- // Switch back to continuous focus after a delay to maintain automatic focus
991
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
992
- self?.returnToContinuousFocus()
993
- }
929
+ // Cancel any existing timer
930
+ continuousFocusReturnTimer?.invalidate()
994
931
 
995
- focusCompletionTimer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { [weak self] _ in
996
- DispatchQueue.main.async {
997
- self?.hideFocusIndicatorWithCompletion()
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
- takePhotoWithAVFoundation()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-plugin-camera-forked",
3
- "version": "3.0.93",
3
+ "version": "3.0.95",
4
4
  "description": "A capacitor camera plugin - A custom Capacitor camera plugin with additional features.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",