diff --git a/docs/docs/guides/RECORDING_VIDEOS.mdx b/docs/docs/guides/RECORDING_VIDEOS.mdx
index 7f5d1ef8f6..99a5dc2f5d 100644
--- a/docs/docs/guides/RECORDING_VIDEOS.mdx
+++ b/docs/docs/guides/RECORDING_VIDEOS.mdx
@@ -54,7 +54,7 @@ camera.current.startRecording({
})
```
-You can customize capture options such as [video codec](/docs/api/interfaces/RecordVideoOptions#videocodec), [video bit-rate](/docs/api/interfaces/RecordVideoOptions#videobitrate), [file type](/docs/api/interfaces/RecordVideoOptions#filetype), [enable flash](/docs/api/interfaces/RecordVideoOptions#flash) and more using the [`RecordVideoOptions`](/docs/api/interfaces/RecordVideoOptions) parameter.
+You can customize capture options such as [video codec](/docs/api/interfaces/RecordVideoOptions#videocodec), [file type](/docs/api/interfaces/RecordVideoOptions#filetype), [enable flash](/docs/api/interfaces/RecordVideoOptions#flash) and more using the [`RecordVideoOptions`](/docs/api/interfaces/RecordVideoOptions) parameter.
For any error that occured _while recording the video_, the `onRecordingError` callback will be invoked with a [`CaptureError`](/docs/api/classes/CameraCaptureError) and the recording is therefore cancelled.
@@ -119,22 +119,16 @@ If the device does not support `h265`, VisionCamera will automatically fall-back
Videos are recorded with a target bit-rate, which the encoder aims to match as closely as possible. A lower bit-rate means less quality (and less file size), a higher bit-rate means higher quality (and larger file size) since it can assign more bits to moving pixels.
-To simply record videos with higher quality, use a [`videoBitRate`](/docs/api/interfaces/RecordVideoOptions#videobitrate) of `'high'`, which effectively increases the bit-rate by 20%:
+To simply record videos with higher quality, use a [`videoBitRate`](/docs/api/interfaces/CameraProps#videobitrate) of `'high'`, which effectively increases the bit-rate by 20%:
-```ts
-camera.current.startRecording({
- ...props,
- videoBitRate: 'high'
-})
+```jsx
+
```
-To use a lower bit-rate for lower quality and lower file-size, use a [`videoBitRate`](/docs/api/interfaces/RecordVideoOptions#videobitrate) of `'low'`, which effectively decreases the bit-rate by 20%:
+To use a lower bit-rate for lower quality and lower file-size, use a [`videoBitRate`](/docs/api/interfaces/CameraProps#videobitrate) of `'low'`, which effectively decreases the bit-rate by 20%:
-```ts
-camera.current.startRecording({
- ...props,
- videoBitRate: 'low'
-})
+```jsx
+
```
#### Custom Bit Rate
@@ -162,13 +156,10 @@ if (codec === 'h265') bitRate *= 0.8 // H.265
bitRate *= yourCustomFactor // e.g. 0.5x for half the bit-rate
```
-And then pass it to the [`startRecording(...)`](/docs/api/classes/Camera#startrecording) function (in Mbps):
+And then pass it to the `` component (in Mbps):
-```ts
-camera.current.startRecording({
- ...props,
- videoBitRate: bitRate // Mbps
-})
+```jsx
+
```
### Video Frame Rate (FPS)
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 4364e792e8..332caf256e 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -7,9 +7,9 @@ PODS:
- hermes-engine (0.75.4):
- hermes-engine/Pre-built (= 0.75.4)
- hermes-engine/Pre-built (0.75.4)
- - MMKV (1.3.9):
- - MMKVCore (~> 1.3.9)
- - MMKVCore (1.3.9)
+ - MMKV (2.0.0):
+ - MMKVCore (~> 2.0.0)
+ - MMKVCore (2.0.0)
- RCT-Folly (2024.01.01.00):
- boost
- DoubleConversion
@@ -2027,8 +2027,8 @@ SPEC CHECKSUMS:
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
glog: 69ef571f3de08433d766d614c73a9838a06bf7eb
hermes-engine: ea92f60f37dba025e293cbe4b4a548fd26b610a0
- MMKV: 817ba1eea17421547e01e087285606eb270a8dcb
- MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9
+ MMKV: f7d1d5945c8765f97f39c3d121f353d46735d801
+ MMKVCore: c04b296010fcb1d1638f2c69405096aac12f6390
RCT-Folly: 4464f4d875961fce86008d45f4ecf6cef6de0740
RCTDeprecation: 726d24248aeab6d7180dac71a936bbca6a994ed1
RCTRequired: a94e7febda6db0345d207e854323c37e3a31d93b
@@ -2098,8 +2098,8 @@ SPEC CHECKSUMS:
RNVectorIcons: 6382277afab3c54658e9d555ee0faa7a37827136
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
VisionCamera: 3b7cfecebbcb3871c4dd3fb47791b96a47fe5371
- Yoga: aa3df615739504eebb91925fc9c58b4922ea9a08
+ Yoga: 055f92ad73f8c8600a93f0e25ac0b2344c3b07e6
-PODFILE CHECKSUM: a43dbce8eba88fb736654cbed5c32f2a764615ef
+PODFILE CHECKSUM: 2ad84241179871ca890f7c65c855d117862f1a68
-COCOAPODS: 1.15.2
+COCOAPODS: 1.16.1
diff --git a/package/android/src/main/java/com/mrousavy/camera/core/CameraConfiguration.kt b/package/android/src/main/java/com/mrousavy/camera/core/CameraConfiguration.kt
index 745ed9d214..f91ce830bb 100644
--- a/package/android/src/main/java/com/mrousavy/camera/core/CameraConfiguration.kt
+++ b/package/android/src/main/java/com/mrousavy/camera/core/CameraConfiguration.kt
@@ -48,7 +48,7 @@ data class CameraConfiguration(
// Output types, those need to be comparable
data class CodeScanner(val codeTypes: List)
data class Photo(val isMirrored: Boolean, val enableHdr: Boolean, val photoQualityBalance: QualityBalance)
- data class Video(val isMirrored: Boolean, val enableHdr: Boolean)
+ data class Video(val isMirrored: Boolean, val enableHdr: Boolean, val bitRateOverride: Double?, val bitRateMultiplier: Double?)
data class FrameProcessor(val isMirrored: Boolean, val pixelFormat: PixelFormat)
data class Audio(val nothing: Unit)
data class Preview(val surfaceProvider: SurfaceProvider)
diff --git a/package/android/src/main/java/com/mrousavy/camera/core/CameraSession+Configuration.kt b/package/android/src/main/java/com/mrousavy/camera/core/CameraSession+Configuration.kt
index 381e9481b5..7c5cf05ae2 100644
--- a/package/android/src/main/java/com/mrousavy/camera/core/CameraSession+Configuration.kt
+++ b/package/android/src/main/java/com/mrousavy/camera/core/CameraSession+Configuration.kt
@@ -22,6 +22,7 @@ import com.mrousavy.camera.core.extensions.*
import com.mrousavy.camera.core.types.CameraDeviceFormat
import com.mrousavy.camera.core.types.Torch
import com.mrousavy.camera.core.types.VideoStabilizationMode
+import com.mrousavy.camera.core.utils.CamcorderProfileUtils
import kotlin.math.roundToInt
private fun assertFormatRequirement(
@@ -44,7 +45,8 @@ private fun assertFormatRequirement(
@SuppressLint("RestrictedApi")
@Suppress("LiftReturnOrAssignment")
internal fun CameraSession.configureOutputs(configuration: CameraConfiguration) {
- Log.i(CameraSession.TAG, "Creating new Outputs for Camera #${configuration.cameraId}...")
+ val cameraId = configuration.cameraId!!
+ Log.i(CameraSession.TAG, "Creating new Outputs for Camera #$cameraId...")
val fpsRange = configuration.targetFpsRange
val format = configuration.format
@@ -120,11 +122,24 @@ internal fun CameraSession.configureOutputs(configuration: CameraConfiguration)
// We are currently not recording, so we can re-create a recorder instance if needed.
Log.i(CameraSession.TAG, "Creating new Recorder...")
Recorder.Builder().also { recorder ->
- configuration.format?.let { format ->
+ format?.let { format ->
recorder.setQualitySelector(format.videoQualitySelector)
}
- // TODO: Make videoBitRate a Camera Prop
- // video.setTargetVideoEncodingBitRate()
+ videoConfig.config.bitRateOverride?.let { bitRateOverride ->
+ val bps = bitRateOverride * 1_000_000
+ recorder.setTargetVideoEncodingBitRate(bps.toInt())
+ }
+ videoConfig.config.bitRateMultiplier?.let { bitRateMultiplier ->
+ if (format == null) {
+ // We need to get the videoSize to estimate the bitRate modifier
+ throw PropRequiresFormatToBeNonNullError("videoBitRate")
+ }
+ val recommendedBitRate = CamcorderProfileUtils.getRecommendedBitRate(cameraId, format.videoSize)
+ if (recommendedBitRate != null) {
+ val targetBitRate = recommendedBitRate.toDouble() * bitRateMultiplier
+ recorder.setTargetVideoEncodingBitRate(targetBitRate.toInt())
+ }
+ }
}.build()
}
diff --git a/package/android/src/main/java/com/mrousavy/camera/core/types/RecordVideoOptions.kt b/package/android/src/main/java/com/mrousavy/camera/core/types/RecordVideoOptions.kt
index 5cf4db27a6..1fca8700be 100644
--- a/package/android/src/main/java/com/mrousavy/camera/core/types/RecordVideoOptions.kt
+++ b/package/android/src/main/java/com/mrousavy/camera/core/types/RecordVideoOptions.kt
@@ -5,23 +5,16 @@ import com.facebook.react.bridge.ReadableMap
import com.mrousavy.camera.core.utils.FileUtils
import com.mrousavy.camera.core.utils.OutputFile
-class RecordVideoOptions(
- val file: OutputFile,
- val videoCodec: VideoCodec,
- val videoBitRateOverride: Double?,
- val videoBitRateMultiplier: Double?
-) {
+class RecordVideoOptions(val file: OutputFile, val videoCodec: VideoCodec) {
companion object {
fun fromJSValue(context: Context, map: ReadableMap): RecordVideoOptions {
val directory = if (map.hasKey("path")) FileUtils.getDirectory(map.getString("path")) else context.cacheDir
val fileType = if (map.hasKey("fileType")) VideoFileType.fromUnionValue(map.getString("fileType")) else VideoFileType.MOV
val videoCodec = if (map.hasKey("videoCodec")) VideoCodec.fromUnionValue(map.getString("videoCodec")) else VideoCodec.H264
- val videoBitRateOverride = if (map.hasKey("videoBitRateOverride")) map.getDouble("videoBitRateOverride") else null
- val videoBitRateMultiplier = if (map.hasKey("videoBitRateMultiplier")) map.getDouble("videoBitRateMultiplier") else null
val outputFile = OutputFile(context, directory, fileType.toExtension())
- return RecordVideoOptions(outputFile, videoCodec, videoBitRateOverride, videoBitRateMultiplier)
+ return RecordVideoOptions(outputFile, videoCodec)
}
}
}
diff --git a/package/android/src/main/java/com/mrousavy/camera/core/utils/CamcorderProfileUtils.kt b/package/android/src/main/java/com/mrousavy/camera/core/utils/CamcorderProfileUtils.kt
index a5946e9a6c..791c412426 100644
--- a/package/android/src/main/java/com/mrousavy/camera/core/utils/CamcorderProfileUtils.kt
+++ b/package/android/src/main/java/com/mrousavy/camera/core/utils/CamcorderProfileUtils.kt
@@ -1,12 +1,16 @@
package com.mrousavy.camera.core.utils
+import android.annotation.SuppressLint
import android.media.CamcorderProfile
import android.os.Build
+import android.util.Log
import android.util.Size
import kotlin.math.abs
class CamcorderProfileUtils {
companion object {
+ private const val TAG = "CamcorderProfileUtils"
+
private fun getResolutionForCamcorderProfileQuality(camcorderProfile: Int): Int =
when (camcorderProfile) {
CamcorderProfile.QUALITY_QCIF -> 176 * 144
@@ -29,6 +33,7 @@ class CamcorderProfileUtils {
val targetResolution = resolution.width * resolution.height
val cameraIdInt = cameraId.toIntOrNull()
+ @SuppressLint("InlinedApi")
var profiles = (CamcorderProfile.QUALITY_QCIF..CamcorderProfile.QUALITY_8KUHD).filter { profile ->
if (cameraIdInt != null) {
return@filter CamcorderProfile.hasProfile(cameraIdInt, profile)
@@ -70,6 +75,7 @@ class CamcorderProfileUtils {
return null
} catch (e: Throwable) {
// some Samsung phones just crash when trying to get the CamcorderProfile. Only god knows why.
+ Log.e(TAG, "Failed to get maximum video size for Camera ID $cameraId! ${e.message}", e)
return null
}
}
@@ -94,6 +100,32 @@ class CamcorderProfileUtils {
return null
} catch (e: Throwable) {
// some Samsung phones just crash when trying to get the CamcorderProfile. Only god knows why.
+ Log.e(TAG, "Failed to get maximum FPS for Camera ID $cameraId! ${e.message}", e)
+ return null
+ }
+ }
+
+ fun getRecommendedBitRate(cameraId: String, videoSize: Size): Int? {
+ try {
+ val quality = findClosestCamcorderProfileQuality(cameraId, videoSize, true)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val profiles = CamcorderProfile.getAll(cameraId, quality)
+ if (profiles != null) {
+ return profiles.videoProfiles.maxOf { profile -> profile.bitrate }
+ }
+ }
+
+ val cameraIdInt = cameraId.toIntOrNull()
+ if (cameraIdInt != null) {
+ val profile = CamcorderProfile.get(cameraIdInt, quality)
+ return profile.videoBitRate
+ }
+
+ return null
+ } catch (e: Throwable) {
+ // some Samsung phones just crash when trying to get the CamcorderProfile. Only god knows why.
+ Log.e(TAG, "Failed to get recommended video bit-rate for Camera ID $cameraId! ${e.message}", e)
return null
}
}
diff --git a/package/android/src/main/java/com/mrousavy/camera/react/CameraView.kt b/package/android/src/main/java/com/mrousavy/camera/react/CameraView.kt
index f0eb5b90de..64a751eaab 100644
--- a/package/android/src/main/java/com/mrousavy/camera/react/CameraView.kt
+++ b/package/android/src/main/java/com/mrousavy/camera/react/CameraView.kt
@@ -37,7 +37,7 @@ import kotlinx.coroutines.launch
// TODO: takePhoto() depth data
// TODO: takePhoto() raw capture
// TODO: takePhoto() return with jsi::Value Image reference for faster capture
-// TODO: Support videoCodec and videoBitRate on Android
+// TODO: Support videoCodec on Android
@SuppressLint("ClickableViewAccessibility", "ViewConstructor", "MissingPermission")
class CameraView(context: Context) :
@@ -75,6 +75,8 @@ class CameraView(context: Context) :
var videoStabilizationMode: VideoStabilizationMode? = null
var videoHdr = false
var photoHdr = false
+ var videoBitRateOverride: Double? = null
+ var videoBitRateMultiplier: Double? = null
// TODO: Use .BALANCED once CameraX fixes it https://issuetracker.google.com/issues/337214687
var photoQualityBalance = QualityBalance.SPEED
@@ -180,7 +182,10 @@ class CameraView(context: Context) :
// Video
if (video || enableFrameProcessor) {
- config.video = CameraConfiguration.Output.Enabled.create(CameraConfiguration.Video(isMirrored, videoHdr))
+ config.video =
+ CameraConfiguration.Output.Enabled.create(
+ CameraConfiguration.Video(isMirrored, videoHdr, videoBitRateOverride, videoBitRateMultiplier)
+ )
} else {
config.video = CameraConfiguration.Output.Disabled.create()
}
diff --git a/package/android/src/main/java/com/mrousavy/camera/react/CameraViewManager.kt b/package/android/src/main/java/com/mrousavy/camera/react/CameraViewManager.kt
index d41300e57d..c5845b727f 100644
--- a/package/android/src/main/java/com/mrousavy/camera/react/CameraViewManager.kt
+++ b/package/android/src/main/java/com/mrousavy/camera/react/CameraViewManager.kt
@@ -191,6 +191,24 @@ class CameraViewManager : ViewGroupManager() {
view.videoHdr = videoHdr
}
+ @ReactProp(name = "videoBitRateOverride", defaultDouble = -1.0)
+ fun setVideoBitRateOverride(view: CameraView, videoBitRateOverride: Double) {
+ if (videoBitRateOverride != -1.0) {
+ view.videoBitRateOverride = videoBitRateOverride
+ } else {
+ view.videoBitRateOverride = null
+ }
+ }
+
+ @ReactProp(name = "videoBitRateMultiplier", defaultDouble = -1.0)
+ fun setVideoBitRateMultiplier(view: CameraView, videoBitRateMultiplier: Double) {
+ if (videoBitRateMultiplier != -1.0) {
+ view.videoBitRateMultiplier = videoBitRateMultiplier
+ } else {
+ view.videoBitRateMultiplier = null
+ }
+ }
+
@ReactProp(name = "lowLightBoost")
fun setLowLightBoost(view: CameraView, lowLightBoost: Boolean) {
view.lowLightBoost = lowLightBoost
diff --git a/package/ios/Core/Types/RecordVideoOptions.swift b/package/ios/Core/Types/RecordVideoOptions.swift
index 65248c45d9..d12e05eb79 100644
--- a/package/ios/Core/Types/RecordVideoOptions.swift
+++ b/package/ios/Core/Types/RecordVideoOptions.swift
@@ -24,7 +24,7 @@ struct RecordVideoOptions {
*/
var bitRateMultiplier: Double?
- init(fromJSValue dictionary: NSDictionary) throws {
+ init(fromJSValue dictionary: NSDictionary, bitRateOverride: Double? = nil, bitRateMultiplier: Double? = nil) throws {
// File Type (.mov or .mp4)
if let fileTypeOption = dictionary["fileType"] as? String {
fileType = try AVFileType(withString: fileTypeOption)
@@ -38,13 +38,9 @@ struct RecordVideoOptions {
codec = try AVVideoCodecType(withString: codecOption)
}
// BitRate Override
- if let parsed = dictionary["videoBitRateOverride"] as? Double {
- bitRateOverride = parsed
- }
+ self.bitRateOverride = bitRateOverride
// BitRate Multiplier
- if let parsed = dictionary["videoBitRateMultiplier"] as? Double {
- bitRateMultiplier = parsed
- }
+ self.bitRateMultiplier = bitRateMultiplier
// Custom Path
let fileExtension = fileType.descriptor ?? "mov"
if let customPath = dictionary["path"] as? String {
diff --git a/package/ios/React/CameraView+RecordVideo.swift b/package/ios/React/CameraView+RecordVideo.swift
index dcb07fd532..913d420efd 100644
--- a/package/ios/React/CameraView+RecordVideo.swift
+++ b/package/ios/React/CameraView+RecordVideo.swift
@@ -16,7 +16,9 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud
let callback = Callback(jsCallback)
do {
- let options = try RecordVideoOptions(fromJSValue: options)
+ let options = try RecordVideoOptions(fromJSValue: options,
+ bitRateOverride: videoBitRateOverride?.doubleValue,
+ bitRateMultiplier: videoBitRateMultiplier?.doubleValue)
// Start Recording with success and error callbacks
cameraSession.startRecording(
diff --git a/package/ios/React/CameraView.swift b/package/ios/React/CameraView.swift
index 6b855381ba..c773975353 100644
--- a/package/ios/React/CameraView.swift
+++ b/package/ios/React/CameraView.swift
@@ -53,6 +53,8 @@ public final class CameraView: UIView, CameraSessionDelegate, PreviewViewDelegat
@objc var photoQualityBalance: NSString?
@objc var lowLightBoost = false
@objc var outputOrientation: NSString?
+ @objc var videoBitRateOverride: NSNumber?
+ @objc var videoBitRateMultiplier: NSNumber?
// other props
@objc var isActive = false
diff --git a/package/ios/React/CameraViewManager.m b/package/ios/React/CameraViewManager.m
index dc3ad1accd..527c9bc0fd 100644
--- a/package/ios/React/CameraViewManager.m
+++ b/package/ios/React/CameraViewManager.m
@@ -47,6 +47,8 @@ @interface RCT_EXTERN_REMAP_MODULE (CameraView, CameraViewManager, RCTViewManage
RCT_EXPORT_VIEW_PROPERTY(lowLightBoost, BOOL);
RCT_EXPORT_VIEW_PROPERTY(videoStabilizationMode, NSString);
RCT_EXPORT_VIEW_PROPERTY(pixelFormat, NSString);
+RCT_EXPORT_VIEW_PROPERTY(videoBitRateOverride, NSNumber);
+RCT_EXPORT_VIEW_PROPERTY(videoBitRateMultiplier, NSNumber);
// other props
RCT_EXPORT_VIEW_PROPERTY(torch, NSString);
RCT_EXPORT_VIEW_PROPERTY(zoom, NSNumber);
@@ -71,37 +73,20 @@ @interface RCT_EXTERN_REMAP_MODULE (CameraView, CameraViewManager, RCTViewManage
RCT_REMAP_VIEW_PROPERTY(onCodeScanned, onCodeScannedEvent, RCTDirectEventBlock);
// Camera View Functions
-RCT_EXTERN_METHOD(startRecording
- : (nonnull NSNumber*)node options
- : (NSDictionary*)options onRecordCallback
- : (RCTResponseSenderBlock)onRecordCallback);
-RCT_EXTERN_METHOD(pauseRecording
- : (nonnull NSNumber*)node resolve
- : (RCTPromiseResolveBlock)resolve reject
- : (RCTPromiseRejectBlock)reject);
-RCT_EXTERN_METHOD(cancelRecording
- : (nonnull NSNumber*)node resolve
- : (RCTPromiseResolveBlock)resolve reject
- : (RCTPromiseRejectBlock)reject);
-RCT_EXTERN_METHOD(resumeRecording
- : (nonnull NSNumber*)node resolve
- : (RCTPromiseResolveBlock)resolve reject
- : (RCTPromiseRejectBlock)reject);
+RCT_EXTERN_METHOD(startRecording : (nonnull NSNumber*)node options : (NSDictionary*)options onRecordCallback : (RCTResponseSenderBlock)
+ onRecordCallback);
+RCT_EXTERN_METHOD(pauseRecording : (nonnull NSNumber*)node resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)
+ reject);
+RCT_EXTERN_METHOD(cancelRecording : (nonnull NSNumber*)node resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)
+ reject);
+RCT_EXTERN_METHOD(resumeRecording : (nonnull NSNumber*)node resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)
+ reject);
RCT_EXTERN_METHOD(stopRecording : (nonnull NSNumber*)node resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject);
-RCT_EXTERN_METHOD(takePhoto
- : (nonnull NSNumber*)node options
- : (NSDictionary*)options resolve
- : (RCTPromiseResolveBlock)resolve reject
- : (RCTPromiseRejectBlock)reject);
-RCT_EXTERN_METHOD(takeSnapshot
- : (nonnull NSNumber*)node options
- : (NSDictionary*)options resolve
- : (RCTPromiseResolveBlock)resolve reject
- : (RCTPromiseRejectBlock)reject);
-RCT_EXTERN_METHOD(focus
- : (nonnull NSNumber*)node point
- : (NSDictionary*)point resolve
- : (RCTPromiseResolveBlock)resolve reject
- : (RCTPromiseRejectBlock)reject);
+RCT_EXTERN_METHOD(takePhoto : (nonnull NSNumber*)node options : (NSDictionary*)options resolve : (RCTPromiseResolveBlock)
+ resolve reject : (RCTPromiseRejectBlock)reject);
+RCT_EXTERN_METHOD(takeSnapshot : (nonnull NSNumber*)node options : (NSDictionary*)options resolve : (RCTPromiseResolveBlock)
+ resolve reject : (RCTPromiseRejectBlock)reject);
+RCT_EXTERN_METHOD(focus : (nonnull NSNumber*)node point : (NSDictionary*)point resolve : (RCTPromiseResolveBlock)
+ resolve reject : (RCTPromiseRejectBlock)reject);
@end
diff --git a/package/src/Camera.tsx b/package/src/Camera.tsx
index cd2041767f..afe056a761 100644
--- a/package/src/Camera.tsx
+++ b/package/src/Camera.tsx
@@ -30,10 +30,7 @@ import { RotationHelper } from './RotationHelper'
export type CameraPermissionStatus = 'granted' | 'not-determined' | 'denied' | 'restricted'
export type CameraPermissionRequestResult = 'granted' | 'denied'
-type NativeRecordVideoOptions = Omit & {
- videoBitRateOverride?: number
- videoBitRateMultiplier?: number
-}
+type NativeRecordVideoOptions = Omit
type RefType = React.Component & Readonly
interface CameraState {
isRecordingWithFlash: boolean
@@ -170,7 +167,7 @@ export class Camera extends React.PureComponent {
}
}
- private getBitRateMultiplier(bitRate: RecordVideoOptions['videoBitRate']): number {
+ private getBitRateMultiplier(bitRate: CameraProps['videoBitRate']): number {
if (typeof bitRate === 'number' || bitRate == null) return 1
switch (bitRate) {
case 'extra-low':
@@ -204,7 +201,7 @@ export class Camera extends React.PureComponent {
* ```
*/
public startRecording(options: RecordVideoOptions): void {
- const { onRecordingError, onRecordingFinished, videoBitRate, ...passThruOptions } = options
+ const { onRecordingError, onRecordingFinished, ...passThruOptions } = options
if (typeof onRecordingError !== 'function' || typeof onRecordingFinished !== 'function')
throw new CameraRuntimeError('parameter/invalid-parameter', 'The onRecordingError or onRecordingFinished functions were not set!')
@@ -215,15 +212,6 @@ export class Camera extends React.PureComponent {
})
}
- const nativeOptions: NativeRecordVideoOptions = passThruOptions
- if (typeof videoBitRate === 'number') {
- // If the user passed an absolute number as a bit-rate, we just use this as a full override.
- nativeOptions.videoBitRateOverride = videoBitRate
- } else if (typeof videoBitRate === 'string' && videoBitRate !== 'normal') {
- // If the user passed 'low'/'normal'/'high', we need to apply this as a multiplier to the native bitrate instead of absolutely setting it
- nativeOptions.videoBitRateMultiplier = this.getBitRateMultiplier(videoBitRate)
- }
-
const onRecordCallback = (video?: VideoFile, error?: CameraCaptureError): void => {
if (this.state.isRecordingWithFlash) {
// disable torch again if it was enabled
@@ -235,9 +223,11 @@ export class Camera extends React.PureComponent {
if (error != null) return onRecordingError(error)
if (video != null) return onRecordingFinished(video)
}
+
+ const nativeRecordVideoOptions: NativeRecordVideoOptions = passThruOptions
try {
// TODO: Use TurboModules to make this awaitable.
- CameraModule.startRecording(this.handle, nativeOptions, onRecordCallback)
+ CameraModule.startRecording(this.handle, nativeRecordVideoOptions, onRecordCallback)
} catch (e) {
throw tryParseNativeCameraError(e)
}
@@ -626,7 +616,7 @@ export class Camera extends React.PureComponent {
/** @internal */
public render(): React.ReactNode {
// We remove the big `device` object from the props because we only need to pass `cameraId` to native.
- const { device, frameProcessor, codeScanner, enableFpsGraph, fps, ...props } = this.props
+ const { device, frameProcessor, codeScanner, enableFpsGraph, fps, videoBitRate, ...props } = this.props
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (device == null) {
@@ -645,6 +635,17 @@ export class Camera extends React.PureComponent {
const minFps = fps == null ? undefined : typeof fps === 'number' ? fps : fps[0]
const maxFps = fps == null ? undefined : typeof fps === 'number' ? fps : fps[1]
+ // bitrate is number (override) or string (multiplier)
+ let bitRateMultiplier: number | undefined
+ let bitRateOverride: number | undefined
+ if (typeof videoBitRate === 'number') {
+ // If the user passed an absolute number as a bit-rate, we just use this as a full override.
+ bitRateOverride = videoBitRate
+ } else if (typeof videoBitRate === 'string' && videoBitRate !== 'normal') {
+ // If the user passed 'low'/'normal'/'high', we need to apply this as a multiplier to the native bitrate instead of absolutely setting it
+ bitRateMultiplier = this.getBitRateMultiplier(videoBitRate)
+ }
+
return (
{
onPreviewStarted={this.onPreviewStarted}
onPreviewStopped={this.onPreviewStopped}
onShutter={this.onShutter}
+ videoBitRateMultiplier={bitRateMultiplier}
+ videoBitRateOverride={bitRateOverride}
onOutputOrientationChanged={this.onOutputOrientationChanged}
onPreviewOrientationChanged={this.onPreviewOrientationChanged}
onError={this.onError}
diff --git a/package/src/NativeCameraView.ts b/package/src/NativeCameraView.ts
index 903fa1016f..b53009ce56 100644
--- a/package/src/NativeCameraView.ts
+++ b/package/src/NativeCameraView.ts
@@ -34,6 +34,7 @@ export type NativeCameraViewProps = Omit<
| 'frameProcessor'
| 'codeScanner'
| 'fps'
+ | 'videoBitRate'
> & {
// private intermediate props
cameraId: string
@@ -41,6 +42,8 @@ export type NativeCameraViewProps = Omit<
codeScannerOptions?: Omit
minFps?: number
maxFps?: number
+ videoBitRateOverride?: number
+ videoBitRateMultiplier?: number
// private events
onViewReady: (event: NativeSyntheticEvent) => void
onAverageFpsChanged?: (event: NativeSyntheticEvent) => void
diff --git a/package/src/types/CameraProps.ts b/package/src/types/CameraProps.ts
index 8056d35c95..18bfc9b618 100644
--- a/package/src/types/CameraProps.ts
+++ b/package/src/types/CameraProps.ts
@@ -200,6 +200,23 @@ export interface CameraProps extends ViewProps {
* Make sure the given {@linkcode format} supports HDR (see {@linkcode CameraDeviceFormat.supportsVideoHdr format.supportsVideoHdr}).
*/
videoHdr?: boolean
+ /**
+ * The bit-rate for encoding the video into a file, in Mbps (Megabits per second).
+ *
+ * Bit-rate is dependant on various factors such as resolution, FPS, pixel format (whether it's 10 bit HDR or not), and video codec.
+ *
+ * By default, it will be calculated by the hardware encoder, which takes all those factors into account.
+ *
+ * * `extra-low`: 40% lower than whatever the hardware encoder recommends.
+ * * `low`: 20% lower than whatever the hardware encoder recommends.
+ * * `normal`: The recommended value by the hardware encoder.
+ * * `high`: 20% higher than whatever the hardware encoder recommends.
+ * * `extra-high`: 40% higher than whatever the hardware encoder recommends.
+ * * `number`: Any custom number for the bit-rate, in Mbps.
+ *
+ * @default 'normal'
+ */
+ videoBitRate?: 'extra-low' | 'low' | 'normal' | 'high' | 'extra-high' | number
/**
* Enables or disables HDR Photo Capture via a double capture routine that combines low- and high exposure photos.
*
diff --git a/package/src/types/VideoFile.ts b/package/src/types/VideoFile.ts
index 197e4d1122..6391df76fa 100644
--- a/package/src/types/VideoFile.ts
+++ b/package/src/types/VideoFile.ts
@@ -33,23 +33,6 @@ export interface RecordVideoOptions {
* - `h265`: The HEVC (High-Efficient-Video-Codec) for higher efficient video recordings. Results in up to 50% smaller file-sizes.
*/
videoCodec?: 'h264' | 'h265'
- /**
- * The bit-rate for encoding the video into a file, in Mbps (Megabits per second).
- *
- * Bit-rate is dependant on various factors such as resolution, FPS, pixel format (whether it's 10 bit HDR or not), and video codec.
- *
- * By default, it will be calculated by the hardware encoder, which takes all those factors into account.
- *
- * * `extra-low`: 40% lower than whatever the hardware encoder recommends.
- * * `low`: 20% lower than whatever the hardware encoder recommends.
- * * `normal`: The recommended value by the hardware encoder.
- * * `high`: 20% higher than whatever the hardware encoder recommends.
- * * `extra-high`: 40% higher than whatever the hardware encoder recommends.
- * * `number`: Any custom number for the bit-rate, in Mbps.
- *
- * @default 'normal'
- */
- videoBitRate?: 'extra-low' | 'low' | 'normal' | 'high' | 'extra-high' | number
}
/**