fix: Fix torch not working on iOS (#2031)

* fix: Fix `torch` not working on iOS

* Format

* fix: Use `withSessionLock` and `withDeviceLock`

* Update CameraSession.swift

* Update RecordingSession.swift
This commit is contained in:
Marc Rousavy 2023-10-18 18:04:58 +02:00 committed by GitHub
parent 6956fded2d
commit 89dfd351e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 55 deletions

View File

@ -70,6 +70,7 @@ class CameraConfiguration {
let orientationChanged: Bool let orientationChanged: Bool
let formatChanged: Bool let formatChanged: Bool
let sidePropsChanged: Bool let sidePropsChanged: Bool
let torchChanged: Bool
let zoomChanged: Bool let zoomChanged: Bool
let audioSessionChanged: Bool let audioSessionChanged: Bool
@ -100,7 +101,9 @@ class CameraConfiguration {
// format (depends on cameraId) // format (depends on cameraId)
formatChanged = inputChanged || left?.format != right.format formatChanged = inputChanged || left?.format != right.format
// side-props (depends on format) // side-props (depends on format)
sidePropsChanged = formatChanged || left?.fps != right.fps || left?.enableLowLightBoost != right.enableLowLightBoost || left?.torch != right.torch sidePropsChanged = formatChanged || left?.fps != right.fps || left?.enableLowLightBoost != right.enableLowLightBoost
// torch (depends on isActive)
torchChanged = left?.isActive != right.isActive || left?.torch != right.torch
// zoom (depends on format) // zoom (depends on format)
zoomChanged = formatChanged || left?.zoom != right.zoom zoomChanged = formatChanged || left?.zoom != right.zoom

View File

@ -175,16 +175,13 @@ extension CameraSession {
/** /**
Configures the active format (`format`) Configures the active format (`format`)
*/ */
func configureFormat(configuration: CameraConfiguration) throws { func configureFormat(configuration: CameraConfiguration, device: AVCaptureDevice) throws {
guard let targetFormat = configuration.format else { guard let targetFormat = configuration.format else {
// No format was set, just use the default. // No format was set, just use the default.
return return
} }
ReactLogger.log(level: .info, message: "Configuring Format (\(targetFormat))...") ReactLogger.log(level: .info, message: "Configuring Format (\(targetFormat))...")
guard let device = videoDeviceInput?.device else {
throw CameraError.session(.cameraNotReady)
}
let currentFormat = CameraDeviceFormat(fromFormat: device.activeFormat) let currentFormat = CameraDeviceFormat(fromFormat: device.activeFormat)
if currentFormat == targetFormat { if currentFormat == targetFormat {
@ -207,13 +204,9 @@ extension CameraSession {
// pragma MARK: Side-Props // pragma MARK: Side-Props
/** /**
Configures format-dependant "side-props" (`fps`, `lowLightBoost`, `torch`) Configures format-dependant "side-props" (`fps`, `lowLightBoost`)
*/ */
func configureSideProps(configuration: CameraConfiguration) throws { func configureSideProps(configuration: CameraConfiguration, device: AVCaptureDevice) throws {
guard let device = videoDeviceInput?.device else {
throw CameraError.session(.cameraNotReady)
}
// Configure FPS // Configure FPS
if let fps = configuration.fps { if let fps = configuration.fps {
let supportsGivenFps = device.activeFormat.videoSupportedFrameRateRanges.contains { range in let supportsGivenFps = device.activeFormat.videoSupportedFrameRateRanges.contains { range in
@ -238,13 +231,20 @@ extension CameraSession {
} }
device.automaticallyEnablesLowLightBoostWhenAvailable = configuration.enableLowLightBoost device.automaticallyEnablesLowLightBoostWhenAvailable = configuration.enableLowLightBoost
} }
}
/**
Configures the torch.
The CaptureSession has to be running for the Torch to work.
*/
func configureTorch(configuration: CameraConfiguration, device: AVCaptureDevice) throws {
// Configure Torch // Configure Torch
let torchMode = configuration.torch.toTorchMode() let torchMode = configuration.torch.toTorchMode()
if device.torchMode != torchMode { if device.torchMode != torchMode {
guard device.hasTorch else { guard device.hasTorch else {
throw CameraError.device(.flashUnavailable) throw CameraError.device(.flashUnavailable)
} }
device.torchMode = torchMode device.torchMode = torchMode
if torchMode == .on { if torchMode == .on {
try device.setTorchModeOn(level: 1.0) try device.setTorchModeOn(level: 1.0)
@ -257,10 +257,7 @@ extension CameraSession {
/** /**
Configures zoom (`zoom`) Configures zoom (`zoom`)
*/ */
func configureZoom(configuration: CameraConfiguration) throws { func configureZoom(configuration: CameraConfiguration, device: AVCaptureDevice) {
guard let device = videoDeviceInput?.device else {
throw CameraError.session(.cameraNotReady)
}
guard let zoom = configuration.zoom else { guard let zoom = configuration.zoom else {
return return
} }

View File

@ -114,10 +114,7 @@ class CameraSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, AVC
do { do {
// If needed, configure the AVCaptureSession (inputs, outputs) // If needed, configure the AVCaptureSession (inputs, outputs)
if difference.isSessionConfigurationDirty { if difference.isSessionConfigurationDirty {
// Lock Capture Session for configuration try self.withSessionLock {
ReactLogger.log(level: .info, message: "Beginning CameraSession configuration...")
self.captureSession.beginConfiguration()
// 1. Update input device // 1. Update input device
if difference.inputChanged { if difference.inputChanged {
try self.configureDevice(configuration: config) try self.configureDevice(configuration: config)
@ -130,40 +127,37 @@ class CameraSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, AVC
if difference.orientationChanged { if difference.orientationChanged {
self.configureOrientation(configuration: config) self.configureOrientation(configuration: config)
} }
}
// Unlock Capture Session again and submit configuration to Hardware
self.captureSession.commitConfiguration()
ReactLogger.log(level: .info, message: "Committed CameraSession configuration!")
} }
// If needed, configure the AVCaptureDevice (format, zoom, low-light-boost, ..) // If needed, configure the AVCaptureDevice (format, zoom, low-light-boost, ..)
if difference.isDeviceConfigurationDirty { if difference.isDeviceConfigurationDirty {
guard let device = self.videoDeviceInput?.device else { try self.withDeviceLock { device in
throw CameraError.session(.cameraNotReady)
}
ReactLogger.log(level: .info, message: "Beginning CaptureDevice configuration...")
try device.lockForConfiguration()
// 4. Configure format // 4. Configure format
if difference.formatChanged { if difference.formatChanged {
try self.configureFormat(configuration: config) try self.configureFormat(configuration: config, device: device)
} }
// 5. Configure side-props (fps, lowLightBoost) // 5. Configure side-props (fps, lowLightBoost)
if difference.sidePropsChanged { if difference.sidePropsChanged {
try self.configureSideProps(configuration: config) try self.configureSideProps(configuration: config, device: device)
} }
// 6. Configure zoom // 6. Configure zoom
if difference.zoomChanged { if difference.zoomChanged {
try self.configureZoom(configuration: config) self.configureZoom(configuration: config, device: device)
}
}
} }
device.unlockForConfiguration() // 7. Start or stop the session if needed
ReactLogger.log(level: .info, message: "Committed CaptureDevice configuration!")
}
// 6. Start or stop the session if needed
self.checkIsActive(configuration: config) self.checkIsActive(configuration: config)
// 8. Enable or disable the Torch if needed (requires session to be running)
if difference.torchChanged {
try self.withDeviceLock { device in
try self.configureTorch(configuration: config, device: device)
}
}
// Update successful, set the new configuration! // Update successful, set the new configuration!
self.configuration = config self.configuration = config
} catch { } catch {
@ -191,6 +185,41 @@ class CameraSession: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, AVC
} }
} }
/**
Runs the given [lambda] under an AVCaptureSession configuration lock (`beginConfiguration()`)
*/
private func withSessionLock(_ lambda: () throws -> Void) throws {
// Lock Capture Session for configuration
ReactLogger.log(level: .info, message: "Beginning CameraSession configuration...")
captureSession.beginConfiguration()
defer {
// Unlock Capture Session again and submit configuration to Hardware
self.captureSession.commitConfiguration()
ReactLogger.log(level: .info, message: "Committed CameraSession configuration!")
}
// Call lambda
try lambda()
}
/**
Runs the given [lambda] under an AVCaptureDevice configuration lock (`lockForConfiguration()`)
*/
private func withDeviceLock(_ lambda: (_ device: AVCaptureDevice) throws -> Void) throws {
guard let device = videoDeviceInput?.device else {
throw CameraError.session(.cameraNotReady)
}
ReactLogger.log(level: .info, message: "Beginning CaptureDevice configuration...")
try device.lockForConfiguration()
defer {
device.unlockForConfiguration()
ReactLogger.log(level: .info, message: "Committed CaptureDevice configuration!")
}
// Call lambda with Device
try lambda(device)
}
/** /**
Starts or stops the CaptureSession if needed (`isActive`) Starts or stops the CaptureSession if needed (`isActive`)
*/ */

View File

@ -36,10 +36,16 @@ class RecordingSession {
private var hasWrittenFirstVideoFrame = false private var hasWrittenFirstVideoFrame = false
private var isFinishing = false private var isFinishing = false
/**
Gets the file URL of the recorded video.
*/
var url: URL { var url: URL {
return assetWriter.outputURL return assetWriter.outputURL
} }
/**
Get the duration (in seconds) of the recorded video.
*/
var duration: Double { var duration: Double {
guard let latestTimestamp = latestTimestamp, guard let latestTimestamp = latestTimestamp,
let initialTimestamp = initialTimestamp else { let initialTimestamp = initialTimestamp else {