feat(android): expose an ability to change the frames per second (#2834)
* feat(android): expose an ability to change the frames per second adding ability to change the video recording frame per second using [MediaRecorder.setVideoFrameRate][1]. [1]: https://developer.android.com/reference/android/media/MediaRecorder#setVideoFrameRate(int) * feat(android): adding getSupportedPreviewFpsRange adding getSupportedPreviewFpsRange method to retrieve supported android devices frame rates for recording. * feat(android): updating docs * feat(android): fix api docs type * feat(android): adding typescript promise type * feat(android): log message for Camera2 * feat(android): iOS error if method invoked Triggering error if android only method getSupportedPreviewFpsRange is invoked on iOS. * feat(android): expose an ability to change the frames per second adding ability to change the video recording frame per second using [MediaRecorder.setVideoFrameRate][1]. [1]: https://developer.android.com/reference/android/media/MediaRecorder#setVideoFrameRate(int) * feat(android): adding getSupportedPreviewFpsRange adding getSupportedPreviewFpsRange method to retrieve supported android devices frame rates for recording. * feat(android): updating docs * feat(android): fix api docs type * feat(android): adding typescript promise type * feat(android): log message for Camera2 * feat(android): iOS error if method invoked Triggering error if android only method getSupportedPreviewFpsRange is invoked on iOS.
This commit is contained in:
parent
0641fc1755
commit
04e6f0674c
@ -801,7 +801,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile, int orientation) {
|
||||
boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile, int orientation, int fps) {
|
||||
|
||||
// make sure compareAndSet is last because we are setting it
|
||||
if (!isPictureCaptureInProgress.get() && mIsRecording.compareAndSet(false, true)) {
|
||||
@ -809,7 +809,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
|
||||
mOrientation = orientation;
|
||||
}
|
||||
try {
|
||||
setUpMediaRecorder(path, maxDuration, maxFileSize, recordAudio, profile);
|
||||
setUpMediaRecorder(path, maxDuration, maxFileSize, recordAudio, profile, fps);
|
||||
mMediaRecorder.prepare();
|
||||
mMediaRecorder.start();
|
||||
|
||||
@ -1497,7 +1497,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
|
||||
mCallback.onFramePreview(data, previewSize.width, previewSize.height, mDeviceOrientation);
|
||||
}
|
||||
|
||||
private void setUpMediaRecorder(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile) {
|
||||
private void setUpMediaRecorder(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile, int fps) {
|
||||
|
||||
mMediaRecorder = new MediaRecorder();
|
||||
mCamera.unlock();
|
||||
@ -1519,7 +1519,7 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
|
||||
camProfile = CamcorderProfile.get(mCameraId, CamcorderProfile.QUALITY_HIGH);
|
||||
}
|
||||
camProfile.videoBitRate = profile.videoBitRate;
|
||||
setCamcorderProfile(camProfile, recordAudio);
|
||||
setCamcorderProfile(camProfile, recordAudio, fps);
|
||||
|
||||
mMediaRecorder.setOrientationHint(calcCameraRotation(mOrientation != Constants.ORIENTATION_AUTO ? orientationEnumToRotation(mOrientation) : mDeviceOrientation));
|
||||
|
||||
@ -1569,9 +1569,29 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,
|
||||
}
|
||||
}
|
||||
|
||||
private void setCamcorderProfile(CamcorderProfile profile, boolean recordAudio) {
|
||||
@Override
|
||||
public ArrayList<int[]> getSupportedPreviewFpsRange() {
|
||||
return (ArrayList<int[]>) mCameraParameters.getSupportedPreviewFpsRange();
|
||||
}
|
||||
|
||||
private boolean isCompatibleWithDevice(int fps) {
|
||||
ArrayList<int[]> validValues;
|
||||
validValues = getSupportedPreviewFpsRange();
|
||||
int accurate_fps = fps * 1000;
|
||||
for(int[] row : validValues) {
|
||||
boolean is_included = accurate_fps >= row[0] && accurate_fps <= row[1];
|
||||
boolean greater_then_zero = accurate_fps > 0;
|
||||
boolean compatible_with_device = is_included && greater_then_zero;
|
||||
if (compatible_with_device) return true;
|
||||
}
|
||||
Log.w("CAMERA_1::", "fps (framePerSecond) received an unsupported value and will be ignored.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setCamcorderProfile(CamcorderProfile profile, boolean recordAudio, int fps) {
|
||||
int compatible_fps = isCompatibleWithDevice(fps) ? fps : profile.videoFrameRate;
|
||||
mMediaRecorder.setOutputFormat(profile.fileFormat);
|
||||
mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
|
||||
mMediaRecorder.setVideoFrameRate(compatible_fps);
|
||||
mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
|
||||
mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
|
||||
mMediaRecorder.setVideoEncoder(profile.videoCodec);
|
||||
|
||||
@ -375,6 +375,13 @@ class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, Me
|
||||
return mFacing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<int[]> getSupportedPreviewFpsRange() {
|
||||
Log.e("CAMERA_2:: ", "getSupportedPreviewFpsRange is not currently supported for Camera2");
|
||||
ArrayList<int[]> validValues = new ArrayList<int[]>();
|
||||
return validValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setCameraId(String id) {
|
||||
if(!ObjectUtils.equals(_mCameraId, id)){
|
||||
@ -562,7 +569,7 @@ class Camera2 extends CameraViewImpl implements MediaRecorder.OnInfoListener, Me
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile, int orientation) {
|
||||
boolean record(String path, int maxDuration, int maxFileSize, boolean recordAudio, CamcorderProfile profile, int orientation, int fps) {
|
||||
if (!mIsRecording) {
|
||||
setUpMediaRecorder(path, maxDuration, maxFileSize, recordAudio, profile);
|
||||
try {
|
||||
|
||||
@ -531,6 +531,10 @@ public class CameraView extends FrameLayout {
|
||||
mImpl.setFlash(flash);
|
||||
}
|
||||
|
||||
public ArrayList<int[]> getSupportedPreviewFpsRange() {
|
||||
return mImpl.getSupportedPreviewFpsRange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current flash mode.
|
||||
*
|
||||
@ -623,8 +627,8 @@ public class CameraView extends FrameLayout {
|
||||
* fires {@link Callback#onRecordingStart(CameraView, String, int, int)} and {@link Callback#onRecordingEnd(CameraView)}.
|
||||
*/
|
||||
public boolean record(String path, int maxDuration, int maxFileSize,
|
||||
boolean recordAudio, CamcorderProfile profile, int orientation) {
|
||||
return mImpl.record(path, maxDuration, maxFileSize, recordAudio, profile, orientation);
|
||||
boolean recordAudio, CamcorderProfile profile, int orientation, int fps) {
|
||||
return mImpl.record(path, maxDuration, maxFileSize, recordAudio, profile, orientation, fps);
|
||||
}
|
||||
|
||||
public void stopRecording() {
|
||||
|
||||
@ -23,6 +23,7 @@ import android.os.Handler;
|
||||
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
@ -61,7 +62,7 @@ abstract class CameraViewImpl {
|
||||
abstract boolean isCameraOpened();
|
||||
|
||||
abstract void setFacing(int facing);
|
||||
|
||||
|
||||
abstract int getFacing();
|
||||
|
||||
abstract void setCameraId(String id);
|
||||
@ -100,7 +101,7 @@ abstract class CameraViewImpl {
|
||||
abstract void takePicture(ReadableMap options);
|
||||
|
||||
abstract boolean record(String path, int maxDuration, int maxFileSize,
|
||||
boolean recordAudio, CamcorderProfile profile, int orientation);
|
||||
boolean recordAudio, CamcorderProfile profile, int orientation, int fps);
|
||||
|
||||
abstract void stopRecording();
|
||||
|
||||
@ -120,6 +121,8 @@ abstract class CameraViewImpl {
|
||||
|
||||
abstract float getZoom();
|
||||
|
||||
abstract public ArrayList<int[]> getSupportedPreviewFpsRange();
|
||||
|
||||
abstract void setWhiteBalance(int whiteBalance);
|
||||
|
||||
abstract int getWhiteBalance();
|
||||
|
||||
@ -20,6 +20,7 @@ import com.google.android.cameraview.Size;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Properties;
|
||||
import java.util.List;
|
||||
@ -422,4 +423,31 @@ public class CameraModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
promise.resolve(false);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getSupportedPreviewFpsRange(final int viewTag, final Promise promise) {
|
||||
final ReactApplicationContext context = getReactApplicationContext();
|
||||
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||
uiManager.addUIBlock(new UIBlock() {
|
||||
@Override
|
||||
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
||||
final RNCameraView cameraView;
|
||||
|
||||
try {
|
||||
cameraView = (RNCameraView) nativeViewHierarchyManager.resolveView(viewTag);
|
||||
WritableArray result = Arguments.createArray();
|
||||
ArrayList<int[]> ranges = cameraView.getSupportedPreviewFpsRange();
|
||||
for (int[] range : ranges) {
|
||||
WritableMap m = new WritableNativeMap();
|
||||
m.putInt("MAXIMUM_FPS", range[0]);
|
||||
m.putInt("MINIMUM_FPS", range[1]);
|
||||
result.pushMap(m);
|
||||
}
|
||||
promise.resolve(result);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,6 +290,7 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
|
||||
String path = options.hasKey("path") ? options.getString("path") : RNFileUtils.getOutputFilePath(cacheDirectory, ".mp4");
|
||||
int maxDuration = options.hasKey("maxDuration") ? options.getInt("maxDuration") : -1;
|
||||
int maxFileSize = options.hasKey("maxFileSize") ? options.getInt("maxFileSize") : -1;
|
||||
int fps = options.hasKey("fps") ? options.getInt("fps") : -1;
|
||||
|
||||
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||
if (options.hasKey("quality")) {
|
||||
@ -309,7 +310,7 @@ public class RNCameraView extends CameraView implements LifecycleEventListener,
|
||||
orientation = options.getInt("orientation");
|
||||
}
|
||||
|
||||
if (RNCameraView.super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile, orientation)) {
|
||||
if (RNCameraView.super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile, orientation, fps)) {
|
||||
mIsRecording = true;
|
||||
mVideoRecordedPromise = promise;
|
||||
} else {
|
||||
|
||||
103
docs/API.md
103
docs/API.md
@ -2,7 +2,9 @@
|
||||
id: api
|
||||
title: Work in progress
|
||||
---
|
||||
|
||||
## Props Index
|
||||
|
||||
[**wip**]
|
||||
|
||||
- [`zoom`](API.md#zoom)
|
||||
@ -18,8 +20,9 @@ title: Work in progress
|
||||
- [`onMountError`](API.md#onMountError)
|
||||
- [`onCameraReady`](API.md#onCameraReady)
|
||||
|
||||
## Methods Index
|
||||
- [`takePictureAsync`](API.md#takePictureAsync())
|
||||
## Methods Index
|
||||
|
||||
- [`takePictureAsync`](<API.md#takePictureAsync()>)
|
||||
- [`recordAsync`](API.md#recordAsync)
|
||||
- [`refreshAuthorizationStatus`](API.md#refreshAuthorizationStatus)
|
||||
- [`stopRecording`](API.md#stopRecording)
|
||||
@ -28,60 +31,69 @@ title: Work in progress
|
||||
- [`getAvailablePictureSizes`](API.md#getAvailablePictureSizes)
|
||||
- [`getSupportedRatiosAsync`](API.md#getSupportedRatiosAsync)
|
||||
- [`isRecording`](API.md#isRecording)
|
||||
- [`getSupportedPreviewFpsRange`](API.md#getSupportedPreviewFpsRange`)
|
||||
|
||||
## Props
|
||||
|
||||
---
|
||||
|
||||
### `zoom`
|
||||
|
||||
This property specifies the zoom value of the camera. Ranges from 0 to 1. Default to 0.
|
||||
|
||||
| Type | Default Value |
|
||||
| ---- | -------- |
|
||||
| number | 0 |
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| number | 0 |
|
||||
|
||||
---
|
||||
|
||||
### `maxZoom`
|
||||
|
||||
The maximum zoom value of the camera. Defaults to 0.
|
||||
The maximum zoom value of the camera. Defaults to 0.
|
||||
|
||||
| Type | Default Value |
|
||||
| ---- | -------- |
|
||||
| number | 0 |
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| number | 0 |
|
||||
|
||||
---
|
||||
|
||||
### `type`
|
||||
|
||||
This property defines which camera on the phone the component is using.
|
||||
Possible values:
|
||||
This property defines which camera on the phone the component is using.
|
||||
Possible values:
|
||||
|
||||
- `front`
|
||||
- `back`
|
||||
|
||||
| Type | Default Value |
|
||||
| ---- | -------- |
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| number | 'back' |
|
||||
|
||||
---
|
||||
|
||||
### `cameraId`
|
||||
|
||||
For selecting from multiple cameras on Android devices. See [2492](https://github.com/react-native-community/react-native-camera/pull/2492) for more info. Can be retrieved with `getCameraIds()`
|
||||
|
||||
| Type | Default Value | Platform |
|
||||
| ---- | -------- | -------- |
|
||||
| String | `null` | Android |
|
||||
| Type | Default Value | Platform |
|
||||
| ------ | ------------- | -------- |
|
||||
| String | `null` | Android |
|
||||
|
||||
---
|
||||
|
||||
### `flashMode`
|
||||
|
||||
Determines the state of the camera flash. Has the following possible states.
|
||||
```off: '1',
|
||||
on: 'auto',
|
||||
auto: 'torch',
|
||||
torch: 'off'
|
||||
```
|
||||
Determines the state of the camera flash. Has the following possible states.
|
||||
|
||||
| Type | Default Value |
|
||||
| ---- | -------- |
|
||||
| object | `{ off: 1 }` |
|
||||
```off: '1',
|
||||
on: 'auto',
|
||||
auto: 'torch',
|
||||
torch: 'off'
|
||||
```
|
||||
|
||||
| Type | Default Value |
|
||||
| ------ | ------------- |
|
||||
| object | `{ off: 1 }` |
|
||||
|
||||
---
|
||||
|
||||
@ -90,11 +102,13 @@ Determines the state of the camera flash. Has the following possible states.
|
||||
## takePictureAsync()
|
||||
|
||||
Returns a promise with TakePictureResponse.
|
||||
|
||||
### Method type
|
||||
|
||||
```ts
|
||||
takePictureAsync(options?: TakePictureOptions): Promise<TakePictureResponse>;
|
||||
```
|
||||
|
||||
```ts
|
||||
interface TakePictureOptions {
|
||||
quality?: number;
|
||||
@ -135,6 +149,7 @@ takePicture = async () => {
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## recordAsync()
|
||||
@ -146,6 +161,7 @@ Returns a promise with RecordResponse.
|
||||
```ts
|
||||
recordAsync(options?: RecordOptions): Promise<RecordResponse>;
|
||||
```
|
||||
|
||||
```ts
|
||||
interface RecordOptions {
|
||||
quality?: keyof VideoQuality;
|
||||
@ -156,6 +172,7 @@ interface RecordOptions {
|
||||
mirrorVideo?: boolean;
|
||||
path?: string;
|
||||
videoBitrate?: number;
|
||||
fps?: number;
|
||||
|
||||
/** iOS only */
|
||||
codec?: keyof VideoCodec | VideoCodec[keyof VideoCodec];
|
||||
@ -170,7 +187,6 @@ interface RecordResponse {
|
||||
/** iOS only */
|
||||
codec: VideoCodec[keyof VideoCodec];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Usage example
|
||||
@ -196,7 +212,6 @@ takeVideo = async () => {
|
||||
|
||||
---
|
||||
|
||||
|
||||
## refreshAuthorizationStatus()
|
||||
|
||||
Allows to make RNCamera check Permissions again and set status accordingly.
|
||||
@ -239,7 +254,6 @@ stopRecording(): void;
|
||||
|
||||
---
|
||||
|
||||
|
||||
## pausePreview()
|
||||
|
||||
Pauses the preview. The preview can be resumed again by using resumePreview().
|
||||
@ -260,7 +274,6 @@ pausePreview(): void;
|
||||
|
||||
---
|
||||
|
||||
|
||||
## resumePreview()
|
||||
|
||||
Resumes the preview after pausePreview() has been called.
|
||||
@ -302,7 +315,6 @@ getAvailablePictureSizes(): Promise<string[]>;
|
||||
|
||||
---
|
||||
|
||||
|
||||
## getSupportedRatiosAsync() - Android only
|
||||
|
||||
Android only. Returns a promise. The promise will be fulfilled with an object with an array containing strings with all camera aspect ratios supported by the device.
|
||||
@ -323,12 +335,12 @@ getSupportedRatiosAsync(): Promise<string[]>;
|
||||
|
||||
---
|
||||
|
||||
|
||||
## isRecording() - iOS only
|
||||
|
||||
iOS only. Returns a promise. The promise will be fulfilled with a boolean indicating if currently recording is started or stopped.
|
||||
|
||||
### Method type
|
||||
|
||||
```ts
|
||||
isRecording(): Promise<boolean>;
|
||||
|
||||
@ -343,4 +355,33 @@ const isRecording = await isRecording();
|
||||
} */
|
||||
```
|
||||
|
||||
- [`getSupportedPreviewFpsRange`](API.md#getSupportedPreviewFpsRange`)
|
||||
|
||||
## getSupportedPreviewFpsRange - Android only
|
||||
|
||||
Android only. Returns a promise. The promise will be fulfilled with a json object including the fps ranges available for those devices ([android docs](<https://developer.android.com/reference/android/hardware/Camera.Parameters#getSupportedPreviewFpsRange()>))
|
||||
|
||||
### Method type
|
||||
|
||||
```ts
|
||||
getSupportedPreviewFpsRange(): Promise<[{MINIMUM_FPS: string, MAXIMUM_FPS: string}]>;
|
||||
|
||||
```
|
||||
|
||||
### Usage example
|
||||
|
||||
```js
|
||||
const previewRange = await this.camera.getSupportedPreviewFpsRange();
|
||||
/* -> [
|
||||
{
|
||||
MINIMUM_FPS: "15000",
|
||||
MAXIMUM_FPS: "15000"
|
||||
},
|
||||
{
|
||||
MINIMUM_FPS: "20000",
|
||||
MAXIMUM_FPS: "20000"
|
||||
}
|
||||
] */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@ -552,6 +552,14 @@ export default class Camera extends React.Component<PropsType, StateType> {
|
||||
}
|
||||
}
|
||||
|
||||
getSupportedPreviewFpsRange = async (): Promise<[]> => {
|
||||
if (Platform.OS === 'android') {
|
||||
return await CameraManager.getSupportedPreviewFpsRange(this._cameraHandle);
|
||||
} else {
|
||||
throw new Error('getSupportedPreviewFpsRange is not supported on iOS');
|
||||
}
|
||||
};
|
||||
|
||||
getAvailablePictureSizes = async (): string[] => {
|
||||
//$FlowFixMe
|
||||
return await CameraManager.getAvailablePictureSizes(this.props.ratio, this._cameraHandle);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user