diff --git a/ios/CameraView+RecordVideo.swift b/ios/CameraView+RecordVideo.swift index a55de8e..ae4e350 100644 --- a/ios/CameraView+RecordVideo.swift +++ b/ios/CameraView+RecordVideo.swift @@ -108,12 +108,18 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud return } + var videoCodec: AVVideoCodecType? + if let codecString = options["videoCodec"] as? String { + videoCodec = AVVideoCodecType(withString: codecString) + } + // Init Video - guard let videoSettings = videoOutput.recommendedVideoSettingsForAssetWriter(writingTo: fileType), + guard let videoSettings = self.recommendedVideoSettings(videoOutput: videoOutput, fileType: fileType, videoCodec: videoCodec), !videoSettings.isEmpty else { callback.reject(error: .capture(.createRecorderError(message: "Failed to get video settings!"))) return } + // get pixel format (420f, 420v, x420) let pixelFormat = CMFormatDescriptionGetMediaSubType(videoInput.device.activeFormat.formatDescription) 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 { let lastPerformanceEvaluationElapsedTime = DispatchTime.now().uptimeNanoseconds - lastFrameProcessorPerformanceEvaluation.uptimeNanoseconds return lastPerformanceEvaluationElapsedTime > 1_000_000_000 diff --git a/ios/CameraViewManager.m b/ios/CameraViewManager.m index 1feb5b1..b02153a 100644 --- a/ios/CameraViewManager.m +++ b/ios/CameraViewManager.m @@ -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(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 diff --git a/ios/CameraViewManager.swift b/ios/CameraViewManager.swift index 8bd2720..ed45691 100644 --- a/ios/CameraViewManager.swift +++ b/ios/CameraViewManager.swift @@ -72,6 +72,26 @@ final class CameraViewManager: RCTViewManager { 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 final func getAvailableCameraDevices(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { withPromise(resolve: resolve, reject: reject) { diff --git a/src/Camera.tsx b/src/Camera.tsx index f5f5beb..55655a2 100644 --- a/src/Camera.tsx +++ b/src/Camera.tsx @@ -1,6 +1,6 @@ import React from 'react'; 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 { ErrorWithCause } 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 { Point } from './Point'; import type { TakeSnapshotOptions } from './Snapshot'; -import type { RecordVideoOptions, VideoFile } from './VideoFile'; +import type { CameraVideoCodec, RecordVideoOptions, VideoFile } from './VideoFile'; //#region Types export type CameraPermissionStatus = 'authorized' | 'not-determined' | 'denied' | 'restricted'; @@ -238,6 +238,22 @@ export class Camera extends React.PureComponent { } //#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 { + try { + return await CameraModule.getAvailableVideoCodecs(this.handle, fileType); + } catch (e) { + throw tryParseNativeCameraError(e); + } + } + //#region Static Functions (NativeModule) /** * Get a list of all available camera devices on the current phone. diff --git a/src/VideoFile.ts b/src/VideoFile.ts index 60414b0..57a31da 100644 --- a/src/VideoFile.ts +++ b/src/VideoFile.ts @@ -3,6 +3,17 @@ import type { TemporaryFile } from './TemporaryFile'; 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 { /** * 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. */ 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; } /**