@@ -30,6 +30,8 @@ final class CaptureSessionController: NSObject {
3030
3131 private( set) var isHDREnabled : Bool = false
3232 private var desiredTorchEnabled : Bool = false
33+ private var cachedZoomFactor : CGFloat = 1.0
34+ private var cachedIsTorchSupported : Bool = false
3335
3436 // Current camera position convenience
3537 var currentPosition : AVCaptureDevice . Position {
@@ -73,6 +75,7 @@ final class CaptureSessionController: NSObject {
7375 // Video device: prefer virtual devices/TrueDepth; default to requested position
7476 let device = try self . findBestCamera ( for: position)
7577 self . videoDevice = device
78+ self . cachedIsTorchSupported = device. hasTorch && device. isTorchAvailable
7679
7780 // Remove existing inputs
7881 if let existingVideo = self . videoDeviceInput {
@@ -130,6 +133,7 @@ final class CaptureSessionController: NSObject {
130133 let maxZ = dev. maxAvailableVideoZoomFactor
131134 let target : CGFloat = max ( minZ, min ( 1.0 , maxZ) )
132135 dev. videoZoomFactor = target
136+ self . cachedZoomFactor = target
133137 dev. unlockForConfiguration ( )
134138 } catch {
135139 // Fallback to async setter if locking fails
@@ -199,6 +203,7 @@ final class CaptureSessionController: NSObject {
199203 let minZ = device. minAvailableVideoZoomFactor
200204 let maxZ = device. maxAvailableVideoZoomFactor
201205 let target = max ( minZ, min ( maxZ, factor) )
206+ self . cachedZoomFactor = target
202207 do {
203208 try device. lockForConfiguration ( )
204209 if animated {
@@ -219,6 +224,7 @@ final class CaptureSessionController: NSObject {
219224 let minZoom = device. minAvailableVideoZoomFactor
220225 let maxZoom = min ( device. maxAvailableVideoZoomFactor, 6.0 ) // Limit for quality
221226 let clamped = max ( minZoom, min ( factor, maxZoom) )
227+ self . cachedZoomFactor = clamped
222228 do {
223229 try device. lockForConfiguration ( )
224230 if animated {
@@ -237,13 +243,7 @@ final class CaptureSessionController: NSObject {
237243 }
238244
239245 func currentZoomFactor( ) -> CGFloat {
240- var value : CGFloat = 1.0
241- sessionQueue. sync {
242- if let device = self . videoDevice {
243- value = device. videoZoomFactor
244- }
245- }
246- return value
246+ return cachedZoomFactor
247247 }
248248
249249
@@ -437,14 +437,17 @@ final class CaptureSessionController: NSObject {
437437 func setPreferredCodecHEVC( _ enabled: Bool ) {
438438 sessionQueue. async {
439439 guard let output = self . movieFileOutput,
440+ !output. isRecording,
440441 let connection = output. connection ( with: . video) else { return }
442+
441443 let available = output. availableVideoCodecTypes
444+
445+ // It's safer to only set this if we're not recording and the codec is available.
446+ // On some devices, setting this can cause a crash if the format doesn't support it.
442447 if enabled, available. contains ( . hevc) {
443448 output. setOutputSettings ( [ AVVideoCodecKey: AVVideoCodecType . hevc] , for: connection)
444449 } else if available. contains ( . h264) {
445450 output. setOutputSettings ( [ AVVideoCodecKey: AVVideoCodecType . h264] , for: connection)
446- } else {
447- output. setOutputSettings ( nil , for: connection)
448451 }
449452 }
450453 }
@@ -490,6 +493,7 @@ final class CaptureSessionController: NSObject {
490493 session. addInput ( newInput)
491494 self . videoDeviceInput = newInput
492495 self . videoDevice = device
496+ self . cachedIsTorchSupported = device. hasTorch && device. isTorchAvailable
493497
494498 // Ensure movie output exists
495499 if self . movieFileOutput == nil {
@@ -528,13 +532,7 @@ final class CaptureSessionController: NSObject {
528532
529533 // MARK: - Torch (Flash)
530534 func isTorchSupported( ) -> Bool {
531- var supported = false
532- sessionQueue. sync {
533- if let device = self . videoDevice {
534- supported = device. hasTorch && device. isTorchAvailable
535- }
536- }
537- return supported
535+ return cachedIsTorchSupported
538536 }
539537
540538 func setTorchEnabled( _ enabled: Bool ) {
0 commit comments