@capgo/camera-preview 7.7.0 → 7.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/android/build.gradle
CHANGED
|
@@ -53,13 +53,13 @@ dependencies {
|
|
|
53
53
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.3.0'
|
|
54
54
|
|
|
55
55
|
// CameraX dependencies
|
|
56
|
-
def camerax_version = "1.5.0-
|
|
56
|
+
def camerax_version = "1.5.0-rc01"
|
|
57
57
|
implementation "androidx.camera:camera-core:${camerax_version}"
|
|
58
58
|
implementation "androidx.camera:camera-camera2:${camerax_version}"
|
|
59
59
|
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
|
|
60
60
|
implementation "androidx.camera:camera-view:${camerax_version}"
|
|
61
61
|
implementation "androidx.camera:camera-extensions:${camerax_version}"
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
testImplementation "junit:junit:$junitVersion"
|
|
64
64
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
65
65
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
@@ -80,6 +80,7 @@ public class CameraPreview
|
|
|
80
80
|
private Location lastLocation;
|
|
81
81
|
private OrientationEventListener orientationListener;
|
|
82
82
|
private int lastOrientation = Configuration.ORIENTATION_UNDEFINED;
|
|
83
|
+
private boolean lastDisableAudio = true;
|
|
83
84
|
|
|
84
85
|
@PluginMethod
|
|
85
86
|
public void getExposureModes(PluginCall call) {
|
|
@@ -689,6 +690,7 @@ public class CameraPreview
|
|
|
689
690
|
final boolean disableAudio = Boolean.TRUE.equals(
|
|
690
691
|
call.getBoolean("disableAudio", true)
|
|
691
692
|
);
|
|
693
|
+
this.lastDisableAudio = disableAudio;
|
|
692
694
|
final String aspectRatio = call.getString("aspectRatio", "4:3");
|
|
693
695
|
final String gridMode = call.getString("gridMode", "none");
|
|
694
696
|
final String positioning = call.getString("positioning", "top");
|
|
@@ -1766,6 +1768,101 @@ public class CameraPreview
|
|
|
1766
1768
|
}
|
|
1767
1769
|
}
|
|
1768
1770
|
|
|
1771
|
+
@PluginMethod
|
|
1772
|
+
public void startRecordVideo(PluginCall call) {
|
|
1773
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
1774
|
+
call.reject("Camera is not running");
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
boolean disableAudio = call.getBoolean("disableAudio") != null
|
|
1779
|
+
? Boolean.TRUE.equals(call.getBoolean("disableAudio"))
|
|
1780
|
+
: this.lastDisableAudio;
|
|
1781
|
+
String permissionAlias = disableAudio
|
|
1782
|
+
? CAMERA_ONLY_PERMISSION_ALIAS
|
|
1783
|
+
: CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
|
|
1784
|
+
|
|
1785
|
+
if (PermissionState.GRANTED.equals(getPermissionState(permissionAlias))) {
|
|
1786
|
+
try {
|
|
1787
|
+
cameraXView.startRecordVideo();
|
|
1788
|
+
call.resolve();
|
|
1789
|
+
} catch (Exception e) {
|
|
1790
|
+
call.reject("Failed to start video recording: " + e.getMessage());
|
|
1791
|
+
}
|
|
1792
|
+
} else {
|
|
1793
|
+
requestPermissionForAlias(
|
|
1794
|
+
permissionAlias,
|
|
1795
|
+
call,
|
|
1796
|
+
"handleVideoRecordingPermissionResult"
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
@PluginMethod
|
|
1802
|
+
public void stopRecordVideo(PluginCall call) {
|
|
1803
|
+
if (cameraXView == null || !cameraXView.isRunning()) {
|
|
1804
|
+
call.reject("Camera is not running");
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
try {
|
|
1809
|
+
bridge.saveCall(call);
|
|
1810
|
+
final String cbId = call.getCallbackId();
|
|
1811
|
+
cameraXView.stopRecordVideo(
|
|
1812
|
+
new CameraXView.VideoRecordingCallback() {
|
|
1813
|
+
@Override
|
|
1814
|
+
public void onSuccess(String filePath) {
|
|
1815
|
+
PluginCall saved = bridge.getSavedCall(cbId);
|
|
1816
|
+
if (saved != null) {
|
|
1817
|
+
JSObject result = new JSObject();
|
|
1818
|
+
result.put("videoFilePath", filePath);
|
|
1819
|
+
saved.resolve(result);
|
|
1820
|
+
bridge.releaseCall(saved);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
@Override
|
|
1825
|
+
public void onError(String message) {
|
|
1826
|
+
PluginCall saved = bridge.getSavedCall(cbId);
|
|
1827
|
+
if (saved != null) {
|
|
1828
|
+
saved.reject("Failed to stop video recording: " + message);
|
|
1829
|
+
bridge.releaseCall(saved);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
);
|
|
1834
|
+
} catch (Exception e) {
|
|
1835
|
+
call.reject("Failed to stop video recording: " + e.getMessage());
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
@PermissionCallback
|
|
1840
|
+
private void handleVideoRecordingPermissionResult(PluginCall call) {
|
|
1841
|
+
// Use the persisted session value to determine which permission we requested
|
|
1842
|
+
String permissionAlias = this.lastDisableAudio
|
|
1843
|
+
? CAMERA_ONLY_PERMISSION_ALIAS
|
|
1844
|
+
: CAMERA_WITH_AUDIO_PERMISSION_ALIAS;
|
|
1845
|
+
|
|
1846
|
+
// Check if either permission is granted (mirroring handleCameraPermissionResult)
|
|
1847
|
+
if (
|
|
1848
|
+
PermissionState.GRANTED.equals(
|
|
1849
|
+
getPermissionState(CAMERA_ONLY_PERMISSION_ALIAS)
|
|
1850
|
+
) ||
|
|
1851
|
+
PermissionState.GRANTED.equals(
|
|
1852
|
+
getPermissionState(CAMERA_WITH_AUDIO_PERMISSION_ALIAS)
|
|
1853
|
+
)
|
|
1854
|
+
) {
|
|
1855
|
+
try {
|
|
1856
|
+
cameraXView.startRecordVideo();
|
|
1857
|
+
call.resolve();
|
|
1858
|
+
} catch (Exception e) {
|
|
1859
|
+
call.reject("Failed to start video recording: " + e.getMessage());
|
|
1860
|
+
}
|
|
1861
|
+
} else {
|
|
1862
|
+
call.reject("Permission denied for video recording");
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1769
1866
|
@PluginMethod
|
|
1770
1867
|
public void getSafeAreaInsets(PluginCall call) {
|
|
1771
1868
|
JSObject ret = new JSObject();
|
|
@@ -56,6 +56,14 @@ import androidx.camera.core.resolutionselector.AspectRatioStrategy;
|
|
|
56
56
|
import androidx.camera.core.resolutionselector.ResolutionSelector;
|
|
57
57
|
import androidx.camera.core.resolutionselector.ResolutionStrategy;
|
|
58
58
|
import androidx.camera.lifecycle.ProcessCameraProvider;
|
|
59
|
+
import androidx.camera.video.FallbackStrategy;
|
|
60
|
+
import androidx.camera.video.FileOutputOptions;
|
|
61
|
+
import androidx.camera.video.Quality;
|
|
62
|
+
import androidx.camera.video.QualitySelector;
|
|
63
|
+
import androidx.camera.video.Recorder;
|
|
64
|
+
import androidx.camera.video.Recording;
|
|
65
|
+
import androidx.camera.video.VideoCapture;
|
|
66
|
+
import androidx.camera.video.VideoRecordEvent;
|
|
59
67
|
import androidx.camera.view.PreviewView;
|
|
60
68
|
import androidx.core.content.ContextCompat;
|
|
61
69
|
import androidx.exifinterface.media.ExifInterface;
|
|
@@ -99,10 +107,19 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
99
107
|
void onCameraStartError(String message);
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
public interface VideoRecordingCallback {
|
|
111
|
+
void onSuccess(String filePath);
|
|
112
|
+
void onError(String message);
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
private ProcessCameraProvider cameraProvider;
|
|
103
116
|
private Camera camera;
|
|
104
117
|
private ImageCapture imageCapture;
|
|
105
118
|
private ImageCapture sampleImageCapture;
|
|
119
|
+
private VideoCapture<Recorder> videoCapture;
|
|
120
|
+
private Recording currentRecording;
|
|
121
|
+
private File currentVideoFile;
|
|
122
|
+
private VideoRecordingCallback currentVideoCallback;
|
|
106
123
|
private PreviewView previewView;
|
|
107
124
|
private GridOverlayView gridOverlayView;
|
|
108
125
|
private FrameLayout previewContainer;
|
|
@@ -725,6 +742,17 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
725
742
|
.setTargetRotation(rotation)
|
|
726
743
|
.build();
|
|
727
744
|
sampleImageCapture = imageCapture;
|
|
745
|
+
|
|
746
|
+
// Setup VideoCapture with rotation and quality fallback
|
|
747
|
+
QualitySelector qualitySelector = QualitySelector.fromOrderedList(
|
|
748
|
+
Arrays.asList(Quality.FHD, Quality.HD, Quality.SD),
|
|
749
|
+
FallbackStrategy.higherQualityOrLowerThan(Quality.FHD)
|
|
750
|
+
);
|
|
751
|
+
Recorder recorder = new Recorder.Builder()
|
|
752
|
+
.setQualitySelector(qualitySelector)
|
|
753
|
+
.build();
|
|
754
|
+
videoCapture = VideoCapture.withOutput(recorder);
|
|
755
|
+
|
|
728
756
|
preview.setSurfaceProvider(previewView.getSurfaceProvider());
|
|
729
757
|
// Unbind any existing use cases and bind new ones
|
|
730
758
|
cameraProvider.unbindAll();
|
|
@@ -732,7 +760,8 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
732
760
|
this,
|
|
733
761
|
currentCameraSelector,
|
|
734
762
|
preview,
|
|
735
|
-
imageCapture
|
|
763
|
+
imageCapture,
|
|
764
|
+
videoCapture
|
|
736
765
|
);
|
|
737
766
|
|
|
738
767
|
// Log details about the active camera
|
|
@@ -3702,4 +3731,103 @@ public class CameraXView implements LifecycleOwner, LifecycleObserver {
|
|
|
3702
3731
|
Log.e(TAG, "triggerAutoFocus: Failed to trigger autofocus", e);
|
|
3703
3732
|
}
|
|
3704
3733
|
}
|
|
3734
|
+
|
|
3735
|
+
public void startRecordVideo() throws Exception {
|
|
3736
|
+
if (videoCapture == null) {
|
|
3737
|
+
throw new Exception("VideoCapture is not initialized");
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3740
|
+
if (currentRecording != null) {
|
|
3741
|
+
throw new Exception("Video recording is already in progress");
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
// Create output file
|
|
3745
|
+
String fileName = "video_" + System.currentTimeMillis() + ".mp4";
|
|
3746
|
+
File outputDir = new File(
|
|
3747
|
+
context.getExternalFilesDir(Environment.DIRECTORY_MOVIES),
|
|
3748
|
+
"CameraPreview"
|
|
3749
|
+
);
|
|
3750
|
+
if (!outputDir.exists()) {
|
|
3751
|
+
outputDir.mkdirs();
|
|
3752
|
+
}
|
|
3753
|
+
currentVideoFile = new File(outputDir, fileName);
|
|
3754
|
+
|
|
3755
|
+
FileOutputOptions outputOptions = new FileOutputOptions.Builder(
|
|
3756
|
+
currentVideoFile
|
|
3757
|
+
).build();
|
|
3758
|
+
|
|
3759
|
+
// Create recording event listener
|
|
3760
|
+
androidx.core.util.Consumer<VideoRecordEvent> videoRecordEventListener =
|
|
3761
|
+
videoRecordEvent -> {
|
|
3762
|
+
if (videoRecordEvent instanceof VideoRecordEvent.Start) {
|
|
3763
|
+
Log.d(TAG, "Video recording started");
|
|
3764
|
+
} else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) {
|
|
3765
|
+
VideoRecordEvent.Finalize finalizeEvent =
|
|
3766
|
+
(VideoRecordEvent.Finalize) videoRecordEvent;
|
|
3767
|
+
handleRecordingFinalized(finalizeEvent);
|
|
3768
|
+
}
|
|
3769
|
+
};
|
|
3770
|
+
|
|
3771
|
+
// Start recording
|
|
3772
|
+
if (sessionConfig != null && !sessionConfig.isDisableAudio()) {
|
|
3773
|
+
currentRecording = videoCapture
|
|
3774
|
+
.getOutput()
|
|
3775
|
+
.prepareRecording(context, outputOptions)
|
|
3776
|
+
.withAudioEnabled()
|
|
3777
|
+
.start(
|
|
3778
|
+
ContextCompat.getMainExecutor(context),
|
|
3779
|
+
videoRecordEventListener
|
|
3780
|
+
);
|
|
3781
|
+
} else {
|
|
3782
|
+
currentRecording = videoCapture
|
|
3783
|
+
.getOutput()
|
|
3784
|
+
.prepareRecording(context, outputOptions)
|
|
3785
|
+
.start(
|
|
3786
|
+
ContextCompat.getMainExecutor(context),
|
|
3787
|
+
videoRecordEventListener
|
|
3788
|
+
);
|
|
3789
|
+
}
|
|
3790
|
+
|
|
3791
|
+
Log.d(
|
|
3792
|
+
TAG,
|
|
3793
|
+
"Video recording started to: " + currentVideoFile.getAbsolutePath()
|
|
3794
|
+
);
|
|
3795
|
+
}
|
|
3796
|
+
|
|
3797
|
+
public void stopRecordVideo(VideoRecordingCallback callback) {
|
|
3798
|
+
if (currentRecording == null) {
|
|
3799
|
+
callback.onError("No video recording in progress");
|
|
3800
|
+
return;
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
// Store the callback to use when recording is finalized
|
|
3804
|
+
currentVideoCallback = callback;
|
|
3805
|
+
currentRecording.stop();
|
|
3806
|
+
|
|
3807
|
+
Log.d(TAG, "Video recording stop requested");
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
private void handleRecordingFinalized(
|
|
3811
|
+
VideoRecordEvent.Finalize finalizeEvent
|
|
3812
|
+
) {
|
|
3813
|
+
if (!finalizeEvent.hasError()) {
|
|
3814
|
+
Log.d(TAG, "Video recording completed successfully");
|
|
3815
|
+
if (currentVideoCallback != null) {
|
|
3816
|
+
String filePath = "file://" + currentVideoFile.getAbsolutePath();
|
|
3817
|
+
currentVideoCallback.onSuccess(filePath);
|
|
3818
|
+
}
|
|
3819
|
+
} else {
|
|
3820
|
+
Log.e(TAG, "Video recording failed: " + finalizeEvent.getError());
|
|
3821
|
+
if (currentVideoCallback != null) {
|
|
3822
|
+
currentVideoCallback.onError(
|
|
3823
|
+
"Video recording failed: " + finalizeEvent.getError()
|
|
3824
|
+
);
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
// Clean up
|
|
3829
|
+
currentRecording = null;
|
|
3830
|
+
currentVideoFile = null;
|
|
3831
|
+
currentVideoCallback = null;
|
|
3832
|
+
}
|
|
3705
3833
|
}
|