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:
fabriziobertoglio1987 2020-05-14 13:47:16 +02:00 committed by GitHub
parent 0641fc1755
commit 04e6f0674c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 43 deletions

View File

@ -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);

View File

@ -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 {

View File

@ -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() {

View File

@ -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();

View File

@ -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();
}
}
});
}
}

View File

@ -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 {

View File

@ -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"
}
] */
```
---

View File

@ -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);