capacitor-plugin-camera-forked 3.0.91 → 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();
|
|
@@ -1151,8 +1013,17 @@ public class CameraPreviewPlugin extends Plugin {
|
|
|
1151
1013
|
|
|
1152
1014
|
@PluginMethod
|
|
1153
1015
|
public void takeSnapshot(PluginCall call) {
|
|
1154
|
-
|
|
1155
|
-
|
|
1016
|
+
if (camera == null) {
|
|
1017
|
+
call.reject("Camera not initialized.");
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
try {
|
|
1022
|
+
call.setKeepAlive(true);
|
|
1023
|
+
takeSnapshotCall = call;
|
|
1024
|
+
} catch (Exception e) {
|
|
1025
|
+
call.reject("Failed to take snapshot: " + e.getMessage());
|
|
1026
|
+
}
|
|
1156
1027
|
}
|
|
1157
1028
|
|
|
1158
1029
|
@PluginMethod
|
|
@@ -33,13 +33,16 @@ 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
|
-
var desiredJpegQuality:
|
|
40
|
+
var desiredJpegQuality: CGFloat = 0.95 // Default to high quality (0.0-1.0)
|
|
38
41
|
|
|
39
42
|
@objc func initialize(_ call: CAPPluginCall) {
|
|
40
43
|
// Get quality parameter from initialization, default to 95% if not specified
|
|
41
44
|
if let quality = call.getInt("quality") {
|
|
42
|
-
desiredJpegQuality =
|
|
45
|
+
desiredJpegQuality = CGFloat(max(1, min(100, quality))) / 100.0
|
|
43
46
|
print("Camera initialized with JPEG quality: \(desiredJpegQuality)")
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -242,6 +245,13 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
242
245
|
self.photoOutput.maxPhotoQualityPrioritization = .quality
|
|
243
246
|
}
|
|
244
247
|
|
|
248
|
+
// Enable content aware distortion correction if available
|
|
249
|
+
if #available(iOS 14.1, *) {
|
|
250
|
+
if self.photoOutput.isContentAwareDistortionCorrectionSupported {
|
|
251
|
+
self.photoOutput.isContentAwareDistortionCorrectionEnabled = true
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
245
255
|
if self.captureSession.canAddOutput(self.photoOutput) {
|
|
246
256
|
self.captureSession.addOutput(photoOutput)
|
|
247
257
|
}
|
|
@@ -335,7 +345,9 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
335
345
|
|
|
336
346
|
// Enable auto-focus and auto-exposure for optimal capture
|
|
337
347
|
if #available(iOS 14.1, *) {
|
|
338
|
-
|
|
348
|
+
if self.photoOutput.isContentAwareDistortionCorrectionEnabled {
|
|
349
|
+
photoSettings.isAutoContentAwareDistortionCorrectionEnabled = true
|
|
350
|
+
}
|
|
339
351
|
}
|
|
340
352
|
|
|
341
353
|
// Enhanced focus before capture for better close-up performance
|
|
@@ -865,51 +877,17 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
865
877
|
x >= 0.0 && x <= 1.0, y >= 0.0 && y <= 1.0 {
|
|
866
878
|
let point = CGPoint(x: CGFloat(x), y: CGFloat(y))
|
|
867
879
|
|
|
868
|
-
//
|
|
869
|
-
if isFocusAnimating {
|
|
870
|
-
resetFocusIfStuck()
|
|
871
|
-
}
|
|
872
|
-
|
|
880
|
+
// Simplified focus without complex throttling or maintenance
|
|
873
881
|
focusWithPoint(point: point)
|
|
874
882
|
|
|
875
|
-
// Calculate the point in the preview layer's coordinate space
|
|
876
|
-
let previewPoint = CGPoint(x: point.x * previewView.bounds.width,
|
|
877
|
-
y: point.y * previewView.bounds.height)
|
|
878
|
-
// showFocusView(at: previewPoint)
|
|
879
883
|
call.resolve()
|
|
880
884
|
} else {
|
|
881
885
|
call.reject("Invalid coordinates. Provide normalized x,y values (0.0-1.0)")
|
|
882
886
|
}
|
|
883
887
|
}
|
|
884
888
|
|
|
885
|
-
private func resetFocusIfStuck() {
|
|
886
|
-
DispatchQueue.main.async { [weak self] in
|
|
887
|
-
guard let self = self else { return }
|
|
888
|
-
|
|
889
|
-
// Remove any existing focus indicator
|
|
890
|
-
self.focusView?.removeFromSuperview()
|
|
891
|
-
self.focusCompletionTimer?.invalidate()
|
|
892
|
-
self.isFocusAnimating = false
|
|
893
|
-
|
|
894
|
-
// Reset focus to continuous mode
|
|
895
|
-
guard let videoInput = self.videoInput else { return }
|
|
896
|
-
let device = videoInput.device
|
|
897
|
-
do {
|
|
898
|
-
try device.lockForConfiguration()
|
|
899
|
-
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
900
|
-
device.focusMode = .continuousAutoFocus
|
|
901
|
-
}
|
|
902
|
-
device.unlockForConfiguration()
|
|
903
|
-
} catch {
|
|
904
|
-
print("Could not reset focus: \(error)")
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
889
|
@objc func resetFocus(_ call: CAPPluginCall) {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
// Reset to center focus
|
|
890
|
+
// Simple reset to center focus
|
|
913
891
|
let centerPoint = CGPoint(x: 0.5, y: 0.5)
|
|
914
892
|
focusWithPoint(point: centerPoint)
|
|
915
893
|
|
|
@@ -921,72 +899,40 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
921
899
|
let convertedPoint = self.previewView.videoPreviewLayer.captureDevicePointConverted(fromLayerPoint: location)
|
|
922
900
|
|
|
923
901
|
focusWithPoint(point: convertedPoint)
|
|
924
|
-
// showFocusView(at: location)
|
|
925
902
|
}
|
|
926
903
|
|
|
927
904
|
func focusWithPoint(point: CGPoint) {
|
|
928
905
|
guard let videoInput = self.videoInput else { return }
|
|
929
906
|
let device = videoInput.device
|
|
930
907
|
|
|
931
|
-
let now = Date()
|
|
932
|
-
if now.timeIntervalSince(lastFocusTime) < focusThrottleInterval {
|
|
933
|
-
return
|
|
934
|
-
}
|
|
935
|
-
lastFocusTime = now
|
|
936
|
-
|
|
937
908
|
do {
|
|
938
909
|
try device.lockForConfiguration()
|
|
939
910
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if device.isFocusPointOfInterestSupported {
|
|
911
|
+
// Cancel any existing focus/exposure operations
|
|
912
|
+
if device.isFocusModeSupported(.autoFocus) && device.isFocusPointOfInterestSupported {
|
|
943
913
|
device.focusPointOfInterest = point
|
|
944
|
-
|
|
945
|
-
// Use autoFocus for more aggressive focusing on specific points
|
|
946
|
-
if device.isFocusModeSupported(.autoFocus) {
|
|
947
|
-
device.focusMode = .autoFocus
|
|
948
|
-
|
|
949
|
-
// Set up observer for focus completion
|
|
950
|
-
NotificationCenter.default.addObserver(
|
|
951
|
-
self,
|
|
952
|
-
selector: #selector(subjectAreaDidChange),
|
|
953
|
-
name: .AVCaptureDeviceSubjectAreaDidChange,
|
|
954
|
-
object: device
|
|
955
|
-
)
|
|
956
|
-
} else if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
957
|
-
device.focusMode = .continuousAutoFocus
|
|
958
|
-
}
|
|
914
|
+
device.focusMode = .autoFocus
|
|
959
915
|
}
|
|
960
916
|
|
|
961
|
-
if device.isExposurePointOfInterestSupported {
|
|
917
|
+
if device.isExposureModeSupported(.autoExpose) && device.isExposurePointOfInterestSupported {
|
|
962
918
|
device.exposurePointOfInterest = point
|
|
963
|
-
|
|
964
|
-
// Use autoExpose for specific point exposure
|
|
965
|
-
if device.isExposureModeSupported(.autoExpose) {
|
|
966
|
-
device.exposureMode = .autoExpose
|
|
967
|
-
} else if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
968
|
-
device.exposureMode = .continuousAutoExposure
|
|
969
|
-
}
|
|
919
|
+
device.exposureMode = .autoExpose
|
|
970
920
|
}
|
|
971
921
|
|
|
972
|
-
// Ensure full focus range is available
|
|
922
|
+
// Ensure full focus range is available
|
|
973
923
|
if device.isAutoFocusRangeRestrictionSupported {
|
|
974
924
|
device.autoFocusRangeRestriction = .none
|
|
975
925
|
}
|
|
976
926
|
|
|
977
|
-
device.isSubjectAreaChangeMonitoringEnabled = true
|
|
978
|
-
|
|
979
927
|
device.unlockForConfiguration()
|
|
980
928
|
|
|
981
|
-
//
|
|
982
|
-
|
|
983
|
-
self?.returnToContinuousFocus()
|
|
984
|
-
}
|
|
929
|
+
// Cancel any existing timer
|
|
930
|
+
continuousFocusReturnTimer?.invalidate()
|
|
985
931
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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()
|
|
990
936
|
}
|
|
991
937
|
|
|
992
938
|
} catch {
|
|
@@ -994,26 +940,16 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
994
940
|
}
|
|
995
941
|
}
|
|
996
942
|
|
|
997
|
-
@objc private func subjectAreaDidChange(notification: NSNotification) {
|
|
998
|
-
DispatchQueue.main.async { [weak self] in
|
|
999
|
-
self?.hideFocusIndicatorWithCompletion()
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
NotificationCenter.default.removeObserver(self, name: .AVCaptureDeviceSubjectAreaDidChange, object: notification.object)
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
943
|
private func returnToContinuousFocus() {
|
|
1006
944
|
guard let videoInput = self.videoInput else { return }
|
|
1007
945
|
let device = videoInput.device
|
|
1008
946
|
do {
|
|
1009
947
|
try device.lockForConfiguration()
|
|
1010
948
|
|
|
1011
|
-
// Return to continuous auto focus for automatic operation
|
|
1012
949
|
if device.isFocusModeSupported(.continuousAutoFocus) {
|
|
1013
950
|
device.focusMode = .continuousAutoFocus
|
|
1014
951
|
}
|
|
1015
952
|
|
|
1016
|
-
// Return to continuous auto exposure
|
|
1017
953
|
if device.isExposureModeSupported(.continuousAutoExposure) {
|
|
1018
954
|
device.exposureMode = .continuousAutoExposure
|
|
1019
955
|
}
|
|
@@ -1024,58 +960,6 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
1024
960
|
}
|
|
1025
961
|
}
|
|
1026
962
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
private func hideFocusIndicatorWithCompletion() {
|
|
1030
|
-
DispatchQueue.main.async { [weak self] in
|
|
1031
|
-
guard let self = self, let focusView = self.focusView else { return }
|
|
1032
|
-
|
|
1033
|
-
UIView.animate(withDuration: 0.2, animations: {
|
|
1034
|
-
focusView.alpha = 0.0
|
|
1035
|
-
focusView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
|
|
1036
|
-
}) { _ in
|
|
1037
|
-
focusView.removeFromSuperview()
|
|
1038
|
-
focusView.transform = CGAffineTransform.identity
|
|
1039
|
-
self.isFocusAnimating = false
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// func showFocusView(at point: CGPoint) {
|
|
1045
|
-
// DispatchQueue.main.async { [weak self] in
|
|
1046
|
-
// guard let self = self else { return }
|
|
1047
|
-
|
|
1048
|
-
// if self.isFocusAnimating {
|
|
1049
|
-
// self.focusView?.removeFromSuperview()
|
|
1050
|
-
// self.focusCompletionTimer?.invalidate()
|
|
1051
|
-
// }
|
|
1052
|
-
|
|
1053
|
-
// // Create focus view if needed - but make it invisible
|
|
1054
|
-
// if self.focusView == nil {
|
|
1055
|
-
// self.focusView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
|
|
1056
|
-
// // Make the focus view completely transparent
|
|
1057
|
-
// self.focusView?.layer.borderColor = UIColor.clear.cgColor
|
|
1058
|
-
// self.focusView?.layer.borderWidth = 0.0
|
|
1059
|
-
// self.focusView?.layer.cornerRadius = 40
|
|
1060
|
-
// self.focusView?.backgroundColor = .clear
|
|
1061
|
-
// self.focusView?.alpha = 0.0
|
|
1062
|
-
|
|
1063
|
-
// // Remove the inner circle to make it completely invisible
|
|
1064
|
-
// // No inner circle added
|
|
1065
|
-
// }
|
|
1066
|
-
|
|
1067
|
-
// self.focusView?.center = point
|
|
1068
|
-
// self.focusView?.alpha = 0.0 // Keep invisible
|
|
1069
|
-
// self.focusView?.transform = CGAffineTransform.identity
|
|
1070
|
-
// self.previewView.addSubview(self.focusView!)
|
|
1071
|
-
|
|
1072
|
-
// self.isFocusAnimating = true
|
|
1073
|
-
|
|
1074
|
-
// // Skip the animation since the view is invisible
|
|
1075
|
-
// // Focus functionality still works, just no visual feedback
|
|
1076
|
-
// }
|
|
1077
|
-
// }
|
|
1078
|
-
|
|
1079
963
|
@objc func requestCameraPermission(_ call: CAPPluginCall) {
|
|
1080
964
|
AVCaptureDevice.requestAccess(for: .video) { granted in
|
|
1081
965
|
DispatchQueue.main.async {
|
|
@@ -1191,14 +1075,59 @@ public class CameraPreviewPlugin: CAPPlugin, AVCaptureVideoDataOutputSampleBuffe
|
|
|
1191
1075
|
}
|
|
1192
1076
|
|
|
1193
1077
|
@objc func takePhoto(_ call: CAPPluginCall) {
|
|
1194
|
-
call.keepAlive = true
|
|
1195
1078
|
takePhotoCall = call
|
|
1196
|
-
|
|
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
|
+
}
|
|
1197
1126
|
}
|
|
1198
1127
|
|
|
1199
1128
|
deinit {
|
|
1200
|
-
NotificationCenter.default.removeObserver(self)
|
|
1201
1129
|
focusCompletionTimer?.invalidate()
|
|
1130
|
+
continuousFocusReturnTimer?.invalidate()
|
|
1202
1131
|
}
|
|
1203
1132
|
|
|
1204
1133
|
}
|
package/package.json
CHANGED