mirror of
https://github.com/status-im/react-native-camera.git
synced 2026-08-31 05:31:07 +00:00
Minor Android MediaRecorder, sizing, and file improvements (#477)
Android MediaRecorder:
- Most importantly, call Camera.unlock() before setting the camera on the
MediaRecorder instance, and release() not just reset() when releasing the MediaRecorder
instance!
- Add comments and notes for preparing and releasing MediaRecorder instance.
- Add onError callback for errors during recording session.
RCTCameraViewManager, RCTCamera, RCTCameraViewFinder, RCTCameraView:
- Implement setCaptureMode, preparing camera based on captureMode. Currently, the only step that
needs to be taken here is setting the recording hint for videos.
- Handle setting _captureMode instance variable where applicable.
Sizing
- Determine ViewFinder supported sizes based on actual captureMode (i.e., get supported picture
sizes when in still capture mode, and get supported video sizes when in video capture mode).
Output files:
- Get appropriate external storage public directory based on media type (image or video).
- Minor variable renaming to indicate that both images or videos can be saved.
README:
- Update captureTarget to indicate that cameraRoll is the actual default for both systems.
- Small clarification for output data type for deprecated memory captureTarget output.
This commit is contained in:
committed by
Zack Story
parent
fb3b12b4f1
commit
b16ec4ab75
@@ -48,7 +48,7 @@ import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
implements MediaRecorder.OnInfoListener, LifecycleEventListener {
|
||||
implements MediaRecorder.OnInfoListener, MediaRecorder.OnErrorListener, LifecycleEventListener {
|
||||
private static final String TAG = "RCTCameraModule";
|
||||
|
||||
public static final int RCT_CAMERA_ASPECT_FILL = 0;
|
||||
@@ -82,7 +82,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
private static ReactApplicationContext _reactContext;
|
||||
private RCTSensorOrientationChecker _sensorOrientationChecker;
|
||||
|
||||
private MediaRecorder mMediaRecorder = new MediaRecorder();
|
||||
private MediaRecorder mMediaRecorder;
|
||||
private long MRStartTime;
|
||||
private File mVideoFile;
|
||||
private Camera mCamera = null;
|
||||
@@ -100,6 +100,16 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
return _reactContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked on new MediaRecorder info.
|
||||
*
|
||||
* See https://developer.android.com/reference/android/media/MediaRecorder.OnInfoListener.html
|
||||
* for more information.
|
||||
*
|
||||
* @param mr MediaRecorder instance for which this callback is being invoked.
|
||||
* @param what Type of info we have received.
|
||||
* @param extra Extra code, specific to the info type.
|
||||
*/
|
||||
public void onInfo(MediaRecorder mr, int what, int extra) {
|
||||
if ( what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
|
||||
what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
|
||||
@@ -109,6 +119,25 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when a MediaRecorder instance encounters an error while recording.
|
||||
*
|
||||
* See https://developer.android.com/reference/android/media/MediaRecorder.OnErrorListener.html
|
||||
* for more information.
|
||||
*
|
||||
* @param mr MediaRecorder instance for which this callback is being invoked.
|
||||
* @param what Type of error that has occurred.
|
||||
* @param extra Extra code, specific to the error type.
|
||||
*/
|
||||
public void onError(MediaRecorder mr, int what, int extra) {
|
||||
// On any error, release the MediaRecorder object and resolve promise. In particular, this
|
||||
// prevents leaving the camera in an unrecoverable state if we crash in the middle of
|
||||
// recording.
|
||||
if (mRecordingPromise != null) {
|
||||
releaseMediaRecorder();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "RCTCameraModule";
|
||||
@@ -222,28 +251,49 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare media recorder for video capture.
|
||||
*
|
||||
* See "Capturing Videos" at https://developer.android.com/guide/topics/media/camera.html for
|
||||
* a guideline of steps and more information in general.
|
||||
*
|
||||
* @param options Options.
|
||||
* @return Throwable; null if no errors.
|
||||
*/
|
||||
private Throwable prepareMediaRecorder(ReadableMap options) {
|
||||
// Prepare CamcorderProfile instance, setting essential options.
|
||||
CamcorderProfile cm = RCTCamera.getInstance().setCaptureVideoQuality(options.getInt("type"), options.getString("quality"));
|
||||
|
||||
// Attach callback to handle maxDuration (@see onInfo method in this file)
|
||||
mMediaRecorder.setOnInfoListener(this);
|
||||
mMediaRecorder.setCamera(mCamera);
|
||||
|
||||
mCamera.unlock(); // make available for mediarecorder
|
||||
|
||||
// Set AV sources
|
||||
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
|
||||
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
|
||||
|
||||
mMediaRecorder.setOrientationHint(RCTCamera.getInstance().getAdjustedDeviceOrientation());
|
||||
|
||||
if (cm == null) {
|
||||
return new RuntimeException("CamcorderProfile not found in prepareMediaRecorder.");
|
||||
}
|
||||
|
||||
// Unlock camera to make available for MediaRecorder. Note that this statement must be
|
||||
// executed before calling setCamera when configuring the MediaRecorder instance.
|
||||
mCamera.unlock();
|
||||
|
||||
// Create new MediaRecorder instance.
|
||||
mMediaRecorder = new MediaRecorder();
|
||||
|
||||
// Attach callback to handle maxDuration (@see onInfo method in this file).
|
||||
mMediaRecorder.setOnInfoListener(this);
|
||||
// Attach error listener (@see onError method in this file).
|
||||
mMediaRecorder.setOnErrorListener(this);
|
||||
|
||||
// Set camera.
|
||||
mMediaRecorder.setCamera(mCamera);
|
||||
|
||||
// Set AV sources.
|
||||
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
|
||||
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
|
||||
|
||||
// Adjust for orientation.
|
||||
mMediaRecorder.setOrientationHint(RCTCamera.getInstance().getAdjustedDeviceOrientation());
|
||||
|
||||
// Set video output format and encoding using CamcorderProfile.
|
||||
cm.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
|
||||
mMediaRecorder.setProfile(cm);
|
||||
|
||||
// Set video output file.
|
||||
mVideoFile = null;
|
||||
switch (options.getInt("target")) {
|
||||
case RCT_CAMERA_CAPTURE_TARGET_MEMORY:
|
||||
@@ -260,11 +310,9 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
mVideoFile = getOutputMediaFile(MEDIA_TYPE_VIDEO);
|
||||
break;
|
||||
}
|
||||
|
||||
if (mVideoFile == null) {
|
||||
return new RuntimeException("Error while preparing output file in prepareMediaRecorder.");
|
||||
}
|
||||
|
||||
mMediaRecorder.setOutputFile(mVideoFile.getPath());
|
||||
|
||||
if (options.hasKey("totalSeconds")) {
|
||||
@@ -277,6 +325,7 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
mMediaRecorder.setMaxFileSize(maxFileSize);
|
||||
}
|
||||
|
||||
// Prepare the MediaRecorder instance with the provided configuration settings.
|
||||
try {
|
||||
mMediaRecorder.prepare();
|
||||
} catch (Exception ex) {
|
||||
@@ -316,6 +365,12 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release media recorder following video capture (or failure to start recording session).
|
||||
*
|
||||
* See "Capturing Videos" at https://developer.android.com/guide/topics/media/camera.html for
|
||||
* a guideline of steps and more information in general.
|
||||
*/
|
||||
private void releaseMediaRecorder() {
|
||||
// Must record at least a second or MediaRecorder throws exceptions on some platforms
|
||||
long duration = System.currentTimeMillis() - MRStartTime;
|
||||
@@ -327,16 +382,30 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
mMediaRecorder.stop(); // stop the recording
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(TAG, "Media recorder stop error.", ex);
|
||||
// Release actual MediaRecorder instance.
|
||||
if (mMediaRecorder != null) {
|
||||
// Stop recording video.
|
||||
try {
|
||||
mMediaRecorder.stop(); // stop the recording
|
||||
} catch (RuntimeException ex) {
|
||||
Log.e(TAG, "Media recorder stop error.", ex);
|
||||
}
|
||||
|
||||
// Optionally, remove the configuration settings from the recorder.
|
||||
mMediaRecorder.reset();
|
||||
|
||||
// Release the MediaRecorder.
|
||||
mMediaRecorder.release();
|
||||
|
||||
// Reset variable.
|
||||
mMediaRecorder = null;
|
||||
}
|
||||
|
||||
mMediaRecorder.reset(); // clear recorder configuration
|
||||
|
||||
// Lock the camera so that future MediaRecorder sessions can use it by calling
|
||||
// Camera.lock(). Note this is not required on Android 4.0+ unless the
|
||||
// MediaRecorder.prepare() call fails.
|
||||
if (mCamera != null) {
|
||||
mCamera.lock(); // relock camera for later use since we unlocked it
|
||||
mCamera.lock();
|
||||
}
|
||||
|
||||
if (mRecordingPromise == null) {
|
||||
@@ -556,7 +625,6 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
return;
|
||||
}
|
||||
|
||||
addToMediaStore(pictureFile.getAbsolutePath());
|
||||
response.putString("path", Uri.fromFile(pictureFile).toString());
|
||||
promise.resolve(response);
|
||||
break;
|
||||
@@ -618,9 +686,20 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
private File getOutputMediaFile(int type) {
|
||||
// Get environment directory type id from requested media type.
|
||||
String environmentDirectoryType;
|
||||
if (type == MEDIA_TYPE_IMAGE) {
|
||||
environmentDirectoryType = Environment.DIRECTORY_PICTURES;
|
||||
} else if (type == MEDIA_TYPE_VIDEO) {
|
||||
environmentDirectoryType = Environment.DIRECTORY_MOVIES;
|
||||
} else {
|
||||
Log.e(TAG, "Unsupported media type:" + type);
|
||||
return null;
|
||||
}
|
||||
|
||||
return getOutputFile(
|
||||
type,
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
|
||||
Environment.getExternalStoragePublicDirectory(environmentDirectoryType)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -641,18 +720,18 @@ public class RCTCameraModule extends ReactContextBaseJavaModule
|
||||
}
|
||||
|
||||
// Create a media file name
|
||||
String photoName = String.format("%s", new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()));
|
||||
String fileName = String.format("%s", new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()));
|
||||
|
||||
if (type == MEDIA_TYPE_IMAGE) {
|
||||
photoName = String.format("IMG_%s.jpg", photoName);
|
||||
fileName = String.format("IMG_%s.jpg", fileName);
|
||||
} else if (type == MEDIA_TYPE_VIDEO) {
|
||||
photoName = String.format("VID_%s.mp4", photoName);
|
||||
fileName = String.format("VID_%s.mp4", fileName);
|
||||
} else {
|
||||
Log.e(TAG, "Unsupported media type:" + type);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new File(String.format("%s%s%s", storageDir.getPath(), File.separator, photoName));
|
||||
return new File(String.format("%s%s%s", storageDir.getPath(), File.separator, fileName));
|
||||
}
|
||||
|
||||
private File getTempMediaFile(int type) {
|
||||
|
||||
Reference in New Issue
Block a user