Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,6 @@ jobs:
-scheme VisionCameraExample \
-sdk iphonesimulator \
-configuration Debug \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-destination 'platform=iOS Simulator,name=iPhone 17' \
build \
CODE_SIGNING_ALLOWED=NO | xcpretty"
2 changes: 1 addition & 1 deletion .github/workflows/validate-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Run clang-format style check
uses: mrousavy/clang-format-action@v1
with:
clang-format-version: '16'
clang-format-version: '21'
check-path: ${{ matrix.path }}
clang-format-style-path: package/.clang-format

Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ std::vector<jsi::PropNameID> FrameHostObject::getPropertyNames(jsi::Runtime& rt)
return result;
}

#define JSI_FUNC [=](jsi::Runtime & runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value
#define JSI_FUNC [=](jsi::Runtime & runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count)->jsi::Value

jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
auto name = propName.utf8(runtime);
Expand Down
11 changes: 8 additions & 3 deletions package/ios/Core/CameraConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,19 @@ final class CameraConfiguration {

// Exposure
var exposure: Float?
var exposureDuration: Double?
var iso: Float?

// isActive (Start/Stop)
var isActive = false

// Audio Session
var audio: OutputConfiguration<Audio> = .disabled

var isManualExposure: Bool {
return exposureDuration != nil || iso != nil
}

init(copyOf other: CameraConfiguration?) {
if let other {
// copy over all values
Expand Down Expand Up @@ -83,7 +89,6 @@ final class CameraConfiguration {
/**
Throw this to abort calls to configure { ... } and apply no changes.
*/
@frozen
enum AbortThrow: Error {
case abort
}
Expand Down Expand Up @@ -132,6 +137,7 @@ final class CameraConfiguration {
formatChanged = inputChanged || left?.format != right.format
// side-props (depends on format)
sidePropsChanged = formatChanged || left?.minFps != right.minFps || left?.maxFps != right.maxFps || left?.enableLowLightBoost != right.enableLowLightBoost
|| left?.exposureDuration != right.exposureDuration || left?.iso != right.iso
// torch (depends on isActive)
let wasInactiveAndNeedsToEnableTorchAgain = left?.isActive == false && right.isActive == true && right.torch == .on
torchChanged = inputChanged || wasInactiveAndNeedsToEnableTorchAgain || left?.torch != right.torch
Expand All @@ -148,12 +154,11 @@ final class CameraConfiguration {
}
}

@frozen
enum OutputConfiguration<T: Equatable>: Equatable {
case disabled
case enabled(config: T)

public static func == (lhs: OutputConfiguration, rhs: OutputConfiguration) -> Bool {
static func == (lhs: OutputConfiguration, rhs: OutputConfiguration) -> Bool {
switch (lhs, rhs) {
case (.disabled, .disabled):
return true
Expand Down
18 changes: 14 additions & 4 deletions package/ios/Core/CameraSession+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,21 @@ extension CameraSession {
}
device.focusMode = .continuousAutoFocus
}
if device.isExposureModeSupported(.continuousAutoExposure) {
if device.isExposurePointOfInterestSupported {
device.exposurePointOfInterest = CGPoint(x: 0.5, y: 0.5)

if configuration.isManualExposure {
// Lock exposure to manual
let durationSeconds = configuration.exposureDuration ?? AVCaptureDevice.currentExposureDuration.seconds
let duration = CMTime(seconds: durationSeconds, preferredTimescale: 1_000_000_000)
let iso = configuration.iso ?? AVCaptureDevice.currentISO
device.setExposureModeCustom(duration: duration, iso: iso)
} else {
// Configure auto-exposure
if device.isExposureModeSupported(.continuousAutoExposure) {
if device.isExposurePointOfInterestSupported {
device.exposurePointOfInterest = CGPoint(x: 0.5, y: 0.5)
}
device.exposureMode = .continuousAutoExposure
}
device.exposureMode = .continuousAutoExposure
}
}

Expand Down
2 changes: 1 addition & 1 deletion package/ios/Core/CameraSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ final class CameraSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegat
}
}

public final func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
final func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
switch captureOutput {
case is AVCaptureVideoDataOutput:
onVideoFrame(sampleBuffer: sampleBuffer, orientation: connection.orientation, isMirrored: connection.isVideoMirrored)
Expand Down
2 changes: 1 addition & 1 deletion package/ios/Core/PreviewView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ final class PreviewView: UIView {
}
}

override public static var layerClass: AnyClass {
override static var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}

Expand Down
2 changes: 1 addition & 1 deletion package/ios/Core/Recording/Track.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ final class Track {
/**
Returns the last timestamp that was actually written to the track.
*/
public private(set) var lastTimestamp: CMTime?
private(set) var lastTimestamp: CMTime?

/**
Gets the natural size of the asset writer, or zero if it is not a visual track.
Expand Down
8 changes: 4 additions & 4 deletions package/ios/Core/Recording/TrackTimeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,22 @@ final class TrackTimeline {
Represents whether the timeline has been marked as finished or not.
A timeline will automatically be marked as finished when a timestamp arrives that appears after a stop().
*/
public private(set) var isFinished = false
private(set) var isFinished = false

/**
Gets the latency of the buffers in this timeline.
This is computed by (currentTime - mostRecentBuffer.timestamp)
*/
public private(set) var latency: CMTime = .zero
private(set) var latency: CMTime = .zero

/**
Get the first actually written timestamp of this timeline
*/
public private(set) var firstTimestamp: CMTime?
private(set) var firstTimestamp: CMTime?
/**
Get the last actually written timestamp of this timeline.
*/
public private(set) var lastTimestamp: CMTime?
private(set) var lastTimestamp: CMTime?

init(ofTrackType type: TrackType, withClock clock: CMClock) {
trackType = type
Expand Down
9 changes: 9 additions & 0 deletions package/ios/Core/Types/CameraDeviceFormat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible {
let minISO: Float
let maxISO: Float

let minExposureDuration: Double
let maxExposureDuration: Double

let fieldOfView: Float

let videoStabilizationModes: [VideoStabilizationMode]
Expand All @@ -44,6 +47,8 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible {
maxFps = format.maxFps
minISO = format.minISO
maxISO = format.maxISO
minExposureDuration = format.minExposureDuration.seconds
maxExposureDuration = format.maxExposureDuration.seconds
fieldOfView = format.videoFieldOfView
videoStabilizationModes = format.videoStabilizationModes.map { VideoStabilizationMode(from: $0) }
autoFocusSystem = AutoFocusSystem(fromFocusSystem: format.autoFocusSystem)
Expand All @@ -62,6 +67,8 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible {
maxFps = jsValue["maxFps"] as! Double
minISO = jsValue["minISO"] as! Float
maxISO = jsValue["maxISO"] as! Float
minExposureDuration = jsValue["minExposureDuration"] as! Double
maxExposureDuration = jsValue["maxExposureDuration"] as! Double
fieldOfView = jsValue["fieldOfView"] as! Float
let jsVideoStabilizationModes = jsValue["videoStabilizationModes"] as! [String]
videoStabilizationModes = try jsVideoStabilizationModes.map { try VideoStabilizationMode(jsValue: $0) }
Expand All @@ -88,6 +95,8 @@ struct CameraDeviceFormat: Equatable, CustomStringConvertible {
"videoWidth": videoWidth,
"minISO": minISO,
"maxISO": maxISO,
"minExposureDuration": minExposureDuration,
"maxExposureDuration": maxExposureDuration,
"fieldOfView": fieldOfView,
"supportsVideoHdr": supportsVideoHdr,
"supportsPhotoHdr": supportsPhotoHdr,
Expand Down
2 changes: 1 addition & 1 deletion package/ios/FrameProcessors/FrameHostObject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
return result;
}

#define JSI_FUNC [=](jsi::Runtime & runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count) -> jsi::Value
#define JSI_FUNC [=](jsi::Runtime & runtime, const jsi::Value& thisValue, const jsi::Value* arguments, size_t count)->jsi::Value

jsi::Value FrameHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
auto name = propName.utf8(runtime);
Expand Down
5 changes: 5 additions & 0 deletions package/ios/React/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ public final class CameraView: UIView, CameraSessionDelegate, PreviewViewDelegat
}
}

@objc var iso: NSNumber?
@objc var exposureDuration: NSNumber?

// events
@objc var onInitializedEvent: RCTDirectEventBlock?
@objc var onErrorEvent: RCTDirectEventBlock?
Expand Down Expand Up @@ -263,6 +266,8 @@ public final class CameraView: UIView, CameraSessionDelegate, PreviewViewDelegat
config.maxFps = maxFps?.int32Value
config.enableLowLightBoost = lowLightBoost
config.torch = try Torch(jsValue: torch)
config.exposureDuration = exposureDuration?.doubleValue
config.iso = iso?.floatValue

// Zoom
config.zoom = zoom.doubleValue
Expand Down
2 changes: 2 additions & 0 deletions package/ios/React/CameraViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ @interface RCT_EXTERN_REMAP_MODULE (CameraView, CameraViewManager, RCTViewManage
RCT_EXPORT_VIEW_PROPERTY(torch, NSString);
RCT_EXPORT_VIEW_PROPERTY(zoom, NSNumber);
RCT_EXPORT_VIEW_PROPERTY(exposure, NSNumber);
RCT_EXPORT_VIEW_PROPERTY(iso, NSNumber);
RCT_EXPORT_VIEW_PROPERTY(exposureDuration, NSNumber);
RCT_EXPORT_VIEW_PROPERTY(enableZoomGesture, BOOL);
RCT_EXPORT_VIEW_PROPERTY(outputOrientation, NSString);
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
Expand Down
2 changes: 1 addition & 1 deletion package/ios/React/Utils/Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Foundation
* Represents a JavaScript Promise instance. `reject()` and `resolve()` should only be called once.
*/
class Promise {
public private(set) var didResolve = false
private(set) var didResolve = false

init(resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
self.resolver = resolver
Expand Down
8 changes: 8 additions & 0 deletions package/src/types/CameraDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ export interface CameraDeviceFormat {
* Minimum supported ISO value
*/
minISO: number
/**
* Minimum supported exposure duration, in seconds
*/
minExposureDuration: number
/**
* Maximum supported exposure duration, in seconds
*/
maxExposureDuration: number
/**
* The video field of view in degrees
*/
Expand Down
17 changes: 17 additions & 0 deletions package/src/types/CameraProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,23 @@ export interface CameraProps extends ViewProps {
* The value between min- and max supported exposure is considered the default, neutral value.
*/
exposure?: number

/**
* Manually configures the ISO value of the Camera.
*
* If either {@linkcode iso} or {@linkcode exposureDuration} is set, the Camera will be locked to manual exposure.
*
* @default undefined
*/
iso?: number
/**
* Manually configures the exposure duration (in seconds) of the Camera.
*
* If either {@linkcode exposureDuration} or {@linkcode iso} is set, the Camera will be locked to manual exposure.
*
* @default undefined
*/
exposureDuration?: number
//#endregion

//#region Format/Preset selection
Expand Down
Loading