feat: Video Codec Option for recording video (#645)
* add video codec value * add types * use `recommendedVideoSettings` method instead * lint * refactor for better readability * add a method to get available codecs (ios) * imrove tsDoc description of the videoCodec option Co-authored-by: Marc Rousavy <marcrousavy@hotmail.com> * ios format Co-authored-by: Marc Rousavy <marcrousavy@hotmail.com>
This commit is contained in:
parent
7460560323
commit
d96c5863c9
@ -108,12 +108,18 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var videoCodec: AVVideoCodecType?
|
||||||
|
if let codecString = options["videoCodec"] as? String {
|
||||||
|
videoCodec = AVVideoCodecType(withString: codecString)
|
||||||
|
}
|
||||||
|
|
||||||
// Init Video
|
// Init Video
|
||||||
guard let videoSettings = videoOutput.recommendedVideoSettingsForAssetWriter(writingTo: fileType),
|
guard let videoSettings = self.recommendedVideoSettings(videoOutput: videoOutput, fileType: fileType, videoCodec: videoCodec),
|
||||||
!videoSettings.isEmpty else {
|
!videoSettings.isEmpty else {
|
||||||
callback.reject(error: .capture(.createRecorderError(message: "Failed to get video settings!")))
|
callback.reject(error: .capture(.createRecorderError(message: "Failed to get video settings!")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get pixel format (420f, 420v, x420)
|
// get pixel format (420f, 420v, x420)
|
||||||
let pixelFormat = CMFormatDescriptionGetMediaSubType(videoInput.device.activeFormat.formatDescription)
|
let pixelFormat = CMFormatDescriptionGetMediaSubType(videoInput.device.activeFormat.formatDescription)
|
||||||
self.recordingSession!.initializeVideoWriter(withSettings: videoSettings,
|
self.recordingSession!.initializeVideoWriter(withSettings: videoSettings,
|
||||||
@ -256,6 +262,14 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func recommendedVideoSettings(videoOutput: AVCaptureVideoDataOutput, fileType: AVFileType, videoCodec: AVVideoCodecType?) -> [String: Any]? {
|
||||||
|
if videoCodec != nil {
|
||||||
|
return videoOutput.recommendedVideoSettings(forVideoCodecType: videoCodec!, assetWriterOutputFileType: fileType)
|
||||||
|
} else {
|
||||||
|
return videoOutput.recommendedVideoSettingsForAssetWriter(writingTo: fileType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var isReadyForNewEvaluation: Bool {
|
private var isReadyForNewEvaluation: Bool {
|
||||||
let lastPerformanceEvaluationElapsedTime = DispatchTime.now().uptimeNanoseconds - lastFrameProcessorPerformanceEvaluation.uptimeNanoseconds
|
let lastPerformanceEvaluationElapsedTime = DispatchTime.now().uptimeNanoseconds - lastFrameProcessorPerformanceEvaluation.uptimeNanoseconds
|
||||||
return lastPerformanceEvaluationElapsedTime > 1_000_000_000
|
return lastPerformanceEvaluationElapsedTime > 1_000_000_000
|
||||||
|
@ -57,4 +57,6 @@ RCT_EXTERN_METHOD(stopRecording:(nonnull NSNumber *)node resolve:(RCTPromiseReso
|
|||||||
RCT_EXTERN_METHOD(takePhoto:(nonnull NSNumber *)node options:(NSDictionary *)options 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(focus:(nonnull NSNumber *)node point:(NSDictionary *)point 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(getAvailableVideoCodecs:(nonnull NSNumber *)node fileType:(NSString *)fileType resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject);
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -72,6 +72,26 @@ final class CameraViewManager: RCTViewManager {
|
|||||||
component.focus(point: CGPoint(x: x.doubleValue, y: y.doubleValue), promise: promise)
|
component.focus(point: CGPoint(x: x.doubleValue, y: y.doubleValue), promise: promise)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc
|
||||||
|
final func getAvailableVideoCodecs(_ node: NSNumber, fileType: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||||
|
withPromise(resolve: resolve, reject: reject) {
|
||||||
|
let component = getCameraView(withTag: node)
|
||||||
|
guard let videoOutput = component.videoOutput else {
|
||||||
|
throw CameraError.session(SessionError.cameraNotReady)
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedFileType = AVFileType.mov
|
||||||
|
if fileType != nil {
|
||||||
|
guard let parsed = try? AVFileType(withString: fileType!) else {
|
||||||
|
throw CameraError.parameter(ParameterError.invalid(unionName: "fileType", receivedValue: fileType!))
|
||||||
|
}
|
||||||
|
parsedFileType = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return videoOutput.availableVideoCodecTypesForAssetWriter(writingTo: parsedFileType).map(\.descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
final func getAvailableCameraDevices(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
final func getAvailableCameraDevices(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||||
withPromise(resolve: resolve, reject: reject) {
|
withPromise(resolve: resolve, reject: reject) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { requireNativeComponent, NativeModules, NativeSyntheticEvent, findNodeHandle, NativeMethods, Platform } from 'react-native';
|
import { requireNativeComponent, NativeModules, NativeSyntheticEvent, findNodeHandle, NativeMethods, Platform } from 'react-native';
|
||||||
import type { FrameProcessorPerformanceSuggestion } from '.';
|
import type { FrameProcessorPerformanceSuggestion, VideoFileType } from '.';
|
||||||
import type { CameraDevice } from './CameraDevice';
|
import type { CameraDevice } from './CameraDevice';
|
||||||
import type { ErrorWithCause } from './CameraError';
|
import type { ErrorWithCause } from './CameraError';
|
||||||
import { CameraCaptureError, CameraRuntimeError, tryParseNativeCameraError, isErrorWithCause } from './CameraError';
|
import { CameraCaptureError, CameraRuntimeError, tryParseNativeCameraError, isErrorWithCause } from './CameraError';
|
||||||
@ -9,7 +9,7 @@ import type { Frame } from './Frame';
|
|||||||
import type { PhotoFile, TakePhotoOptions } from './PhotoFile';
|
import type { PhotoFile, TakePhotoOptions } from './PhotoFile';
|
||||||
import type { Point } from './Point';
|
import type { Point } from './Point';
|
||||||
import type { TakeSnapshotOptions } from './Snapshot';
|
import type { TakeSnapshotOptions } from './Snapshot';
|
||||||
import type { RecordVideoOptions, VideoFile } from './VideoFile';
|
import type { CameraVideoCodec, RecordVideoOptions, VideoFile } from './VideoFile';
|
||||||
|
|
||||||
//#region Types
|
//#region Types
|
||||||
export type CameraPermissionStatus = 'authorized' | 'not-determined' | 'denied' | 'restricted';
|
export type CameraPermissionStatus = 'authorized' | 'not-determined' | 'denied' | 'restricted';
|
||||||
@ -238,6 +238,22 @@ export class Camera extends React.PureComponent<CameraProps> {
|
|||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of video codecs the current camera supports for a given file type. Returned values are ordered by efficiency (descending).
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const codecs = await camera.current.getAvailableVideoCodecs("mp4")
|
||||||
|
* ```
|
||||||
|
* @throws {@linkcode CameraRuntimeError} When any kind of error occured while getting available video codecs. Use the {@linkcode ParameterError.code | code} property to get the actual error
|
||||||
|
*/
|
||||||
|
public async getAvailableVideoCodecs(fileType?: VideoFileType): Promise<CameraVideoCodec[]> {
|
||||||
|
try {
|
||||||
|
return await CameraModule.getAvailableVideoCodecs(this.handle, fileType);
|
||||||
|
} catch (e) {
|
||||||
|
throw tryParseNativeCameraError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//#region Static Functions (NativeModule)
|
//#region Static Functions (NativeModule)
|
||||||
/**
|
/**
|
||||||
* Get a list of all available camera devices on the current phone.
|
* Get a list of all available camera devices on the current phone.
|
||||||
|
@ -3,6 +3,17 @@ import type { TemporaryFile } from './TemporaryFile';
|
|||||||
|
|
||||||
export type VideoFileType = 'mov' | 'avci' | 'm4v' | 'mp4';
|
export type VideoFileType = 'mov' | 'avci' | 'm4v' | 'mp4';
|
||||||
|
|
||||||
|
export type CameraVideoCodec =
|
||||||
|
| 'h264'
|
||||||
|
| 'hevc'
|
||||||
|
| 'hevc-alpha'
|
||||||
|
| 'jpeg'
|
||||||
|
| 'pro-res-4444'
|
||||||
|
| 'pro-res-422'
|
||||||
|
| 'pro-res-422-hq'
|
||||||
|
| 'pro-res-422-lt'
|
||||||
|
| 'pro-res-422-proxy';
|
||||||
|
|
||||||
export interface RecordVideoOptions {
|
export interface RecordVideoOptions {
|
||||||
/**
|
/**
|
||||||
* Set the video flash mode. Natively, this just enables the torch while recording.
|
* Set the video flash mode. Natively, this just enables the torch while recording.
|
||||||
@ -21,6 +32,13 @@ export interface RecordVideoOptions {
|
|||||||
* Called when the recording has been successfully saved to file.
|
* Called when the recording has been successfully saved to file.
|
||||||
*/
|
*/
|
||||||
onRecordingFinished: (video: VideoFile) => void;
|
onRecordingFinished: (video: VideoFile) => void;
|
||||||
|
/**
|
||||||
|
* Set the video codec to record in. Different video codecs affect video quality and video size.
|
||||||
|
* To get a list of all available video codecs use the `getAvailableVideoCodecs()` function.
|
||||||
|
*
|
||||||
|
* @default undefined
|
||||||
|
*/
|
||||||
|
videoCodec?: CameraVideoCodec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user