diff --git a/plugin.xml b/plugin.xml
index 297a332..2a13972 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -19,6 +19,7 @@
+
diff --git a/src/android/CameraPreviewFragment.java b/src/android/CameraPreviewFragment.java
index c0a299f..bd34780 100644
--- a/src/android/CameraPreviewFragment.java
+++ b/src/android/CameraPreviewFragment.java
@@ -1,13 +1,17 @@
package com.spoon.simplecamerapreview;
+import android.Manifest;
import android.annotation.SuppressLint;
+import android.content.ContentValues;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.hardware.camera2.CameraCharacteristics;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.MediaStore;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -29,8 +33,17 @@
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.video.FileOutputOptions;
+import androidx.camera.video.Quality;
+import androidx.camera.video.QualitySelector;
+import androidx.camera.video.Recorder;
+import androidx.camera.video.Recording;
+import androidx.camera.video.VideoCapture;
+import androidx.camera.video.VideoRecordEvent;
import androidx.camera.view.PreviewView;
+import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
+
import androidx.exifinterface.media.ExifInterface;
import androidx.fragment.app.Fragment;
@@ -51,6 +64,11 @@ interface CameraCallback {
void onCompleted(Exception err, String nativePath);
}
+interface VideoCallback {
+ void onStart(Boolean recording, String nativePath);
+ void onStop(Boolean recording, String nativePath);
+}
+
interface CameraStartedCallback {
void onCameraStarted(Exception err);
}
@@ -76,6 +94,9 @@ public class CameraPreviewFragment extends Fragment {
private PreviewView viewFinder;
private Preview preview;
private ImageCapture imageCapture;
+ private VideoCapture videoCapture;
+ Recording recording = null;
+ ProcessCameraProvider cameraProvider = null;
private Camera camera;
private CameraStartedCallback startCameraCallback;
private Location location;
@@ -120,13 +141,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
viewFinder.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
containerView.addView(viewFinder);
startCamera();
-
return containerView;
}
public void startCamera() {
ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(getActivity());
- ProcessCameraProvider cameraProvider = null;
try {
cameraProvider = cameraProviderFuture.get();
@@ -136,8 +155,8 @@ public void startCamera() {
startCameraCallback.onCameraStarted(new Exception("Unable to start camera"));
return;
}
- setUpCamera(captureDevice,cameraProvider);
+ setUpCamera(captureDevice,cameraProvider);
preview.setSurfaceProvider(viewFinder.getSurfaceProvider());
if (startCameraCallback != null) {
@@ -258,6 +277,60 @@ public void hasFlash(HasFlashCallback hasFlashCallback) {
hasFlashCallback.onResult(camera.getCameraInfo().hasFlashUnit());
}
+ public void stopCaptureVideo() {
+ if (recording != null) {
+ recording.stop();
+ recording = null;
+ }
+ }
+
+ public void captureVideo(VideoCallback videoCallback) {
+ if (recording != null) {
+ recording.stop();
+ recording = null;
+ return;
+ }
+ UUID uuid = UUID.randomUUID();
+
+ String filename = uuid.toString() + ".mp4";
+ if (ActivityCompat.checkSelfPermission(this.getContext(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this.getActivity(), new String[]{Manifest.permission.RECORD_AUDIO}, 200);
+ }
+ File videoFile = new File(
+ getContext().getFilesDir(),
+ filename
+ );
+
+ FileOutputOptions outputOptions = new FileOutputOptions.Builder(videoFile).build();
+
+ recording = videoCapture.getOutput()
+ .prepareRecording(this.getContext().getApplicationContext(), outputOptions)
+ .withAudioEnabled()
+ .start(ContextCompat.getMainExecutor(this.getContext()), videoRecordEvent -> {
+ if (videoRecordEvent instanceof VideoRecordEvent.Start) {
+ videoCallback.onStart(true, null);
+ } else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) {
+ VideoRecordEvent.Finalize finalizeEvent = (VideoRecordEvent.Finalize) videoRecordEvent;
+ if (finalizeEvent.hasError()) {
+ // Handle the error
+ int errorCode = finalizeEvent.getError();
+ Throwable errorCause = finalizeEvent.getCause();
+ Log.e(TAG, "Video recording error: " + errorCode, errorCause);
+ } else {
+ // Handle video saved
+ videoCallback.onStop(false, Uri.fromFile(videoFile).toString());
+ Uri savedUri = finalizeEvent.getOutputResults().getOutputUri();
+ Log.i(TAG, "Video saved to: " + savedUri);
+ }
+ recording = null;
+ }
+ // Other event types can be handled if needed
+ });
+
+ }
+
+
+
public void takePicture(boolean useFlash, CameraCallback takePictureCallback) {
if (torchActivated) {
useFlash = true;
@@ -398,18 +471,24 @@ public void setUpCamera(String captureDevice, ProcessCameraProvider cameraProvid
targetResolution = CameraPreviewFragment.calculateResolution(getContext(), targetSize);
}
+ Recorder recorder = new Recorder.Builder()
+ .setQualitySelector(QualitySelector.from(Quality.LOWEST))
+ .build();
+ videoCapture = VideoCapture.withOutput(recorder);
+
+
preview = new Preview.Builder().build();
imageCapture = new ImageCapture.Builder()
.setTargetResolution(targetResolution)
.build();
-
cameraProvider.unbindAll();
try {
camera = cameraProvider.bindToLifecycle(
getActivity(),
cameraSelector,
preview,
- imageCapture
+ imageCapture,
+ videoCapture
);
} catch (IllegalArgumentException e) {
// Error with result in capturing image with default resolution
@@ -420,9 +499,9 @@ public void setUpCamera(String captureDevice, ProcessCameraProvider cameraProvid
getActivity(),
cameraSelector,
preview,
- imageCapture
+ imageCapture,
+ videoCapture
);
}
-
}
}
diff --git a/src/android/SimpleCameraPreview.java b/src/android/SimpleCameraPreview.java
index 4bc7b40..8d3c87c 100644
--- a/src/android/SimpleCameraPreview.java
+++ b/src/android/SimpleCameraPreview.java
@@ -43,12 +43,15 @@ public class SimpleCameraPreview extends CordovaPlugin {
private LocationListener mLocationCallback;
private ViewParent webViewParent;
+ private CallbackContext videoCallback;
+
private static final int containerViewId = 20;
private static final int DIRECTION_FRONT = 0;
private static final int DIRECTION_BACK = 1;
private static final int REQUEST_CODE_PERMISSIONS = 4582679;
private static final String[] REQUIRED_PERMISSIONS = {Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION};
+
public SimpleCameraPreview() {
super();
}
@@ -72,6 +75,13 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
case "torchSwitch":
return torchSwitch(args.getBoolean(0), callbackContext);
+ case "captureVideo":
+ return captureVideo(callbackContext);
+
+ case "stopCaptureVideo":
+ this.videoStopCallback = callbackContext;
+ return stopCaptureVideo(callbackContext);
+
case "deviceHasFlash":
return deviceHasFlash(callbackContext);
@@ -267,6 +277,64 @@ public void fetchLocation() {
}
}
+ CallbackContext videoStopCallback;
+
+ private boolean stopCaptureVideo(CallbackContext callbackContext) {
+ if (fragment == null) {
+ callbackContext.error("Camera is closed");
+ return true;
+ }
+ fragment.stopCaptureVideo();
+ return true;
+ }
+ private boolean captureVideo(CallbackContext callbackContext) {
+ if (fragment == null) {
+ callbackContext.error("Camera is closed");
+ return true;
+ }
+
+ fragment.captureVideo(new VideoCallback() {
+ public void onStart(Boolean recording, String nativePath) {
+ JSONObject data = new JSONObject();
+ if (recording) {
+ try {
+ data.put("recording", true);
+ data.put("nativePath", null);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ callbackContext.error("Cannot send recording data");
+ return;
+ }
+
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, data);
+// pluginResult.setKeepCallback(true);
+ callbackContext.sendPluginResult(pluginResult);
+ }
+ }
+
+ public void onStop(Boolean recording, String nativePath) {
+ JSONObject data = new JSONObject();
+ try {
+ data.put("recording", false);
+ data.put("nativePath", nativePath);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ callbackContext.error("Cannot send recording data");
+ return;
+ }
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, data);
+// pluginResult.setKeepCallback(true);
+ if (videoStopCallback != null) {
+ videoStopCallback.sendPluginResult(pluginResult);
+ videoStopCallback = null;
+ return;
+ }
+ callbackContext.sendPluginResult(pluginResult);
+ }
+ });
+ return true;
+ }
+
private boolean capture(boolean useFlash, CallbackContext callbackContext) {
if (fragment == null) {
callbackContext.error("Camera is closed");
diff --git a/www/SimpleCameraPreview.js b/www/SimpleCameraPreview.js
index 7b1b9e8..9d4333f 100755
--- a/www/SimpleCameraPreview.js
+++ b/www/SimpleCameraPreview.js
@@ -20,6 +20,14 @@ SimpleCameraPreview.capture = function (options, onSuccess, onError) {
exec(onSuccess, onError, PLUGIN_NAME, "capture", [options.flash]);
};
+SimpleCameraPreview.captureVideo = function (onSuccess, onError) {
+ exec(onSuccess, onError, PLUGIN_NAME, "captureVideo");
+};
+
+SimpleCameraPreview.stopCaptureVideo = function (onSuccess, onError) {
+ exec(onSuccess, onError, PLUGIN_NAME, "stopCaptureVideo");
+};
+
SimpleCameraPreview.setSize = function (options, onSuccess, onError) {
exec(onSuccess, onError, PLUGIN_NAME, "setSize", [options]);
};