Hi Greg @DTS Engineer ,
Thanks again for your response.
I understand the value of a focused sample project, but to save time, I’ve included an expanded and representative code snippet directly from my CameraManager. I hope this helps clarify what I’m doing.
In this feature, the user places a finger over the telephoto lens for PPG-based heart rate detection. I explicitly select the telephoto lens using:
AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back)
Then I attempt to lock lens switching with:
device.setPrimaryConstituentDeviceSwitchingBehavior(.locked, restrictedSwitchingBehaviorConditions: [])
I also lock focus, exposure, white balance, HDR, and low light boost. I stream frame data (average RGB) to a WKWebView via evaluateJavaScript() on a repeating timer.
However, on iPhone 16 Pro Max, I still occasionally see:
Lens switching (usually in low light or when the finger fully covers the telephoto)
Or worse, the camera silently stops delivering frames (through captureOutput(_:didOutput:) )
Here’s the simplified but representative core setup:
private func setupCamera() {
videoDevice = AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back)
if #available(iOS 15.0, *), let device = videoDevice,
device.activePrimaryConstituentDeviceSwitchingBehavior != .unsupported {
do {
try device.lockForConfiguration()
device.setPrimaryConstituentDeviceSwitchingBehavior(.locked, restrictedSwitchingBehaviorConditions: [])
device.unlockForConfiguration()
} catch {
print("Lens lock failed: \(error)")
}
}
}
func startCamera(in view: UIView) {
captureSession = AVCaptureSession()
guard let captureSession, let device = videoDevice else { return }
do {
let input = try AVCaptureDeviceInput(device: device)
if captureSession.canAddInput(input) {
captureSession.addInput(input)
}
videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
videoOutput.alwaysDiscardsLateVideoFrames = true
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "camera.queue"))
if captureSession.canAddOutput(videoOutput) {
captureSession.addOutput(videoOutput)
}
try device.lockForConfiguration()
device.focusMode = .locked
device.exposureMode = .locked
device.whiteBalanceMode = .locked
device.automaticallyAdjustsVideoHDREnabled = false
if device.isLowLightBoostSupported {
device.automaticallyEnablesLowLightBoostWhenAvailable = false
}
// Set 60 FPS 1080p format
if let format = device.formats.first(where: {
CMVideoFormatDescriptionGetDimensions($0.formatDescription) == CMVideoDimensions(width: 1920, height: 1080) &&
$0.videoSupportedFrameRateRanges.first?.maxFrameRate ?? 0 >= 60
}) {
device.activeFormat = format
device.activeVideoMinFrameDuration = CMTime(value: 1, timescale: 60)
device.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: 60)
}
device.unlockForConfiguration()
captureSession.startRunning()
} catch {
print("Camera setup error: \(error)")
}
startSendingColorUpdatesToWebView()
}
And inside the frame callback:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let (r, g, b) = computeAverageColor(from: pixelBuffer) ?? (0, 0, 0)
let timestampMs = Int(Date().timeIntervalSince1970 * 1000)
let js = """
window.TurboNativeBridge.changeFingerAvgColorInIos(\(r), \(g), \(b), \(timestampMs))
"""
DispatchQueue.main.async {
self.webView?.evaluateJavaScript(js)
}
}
My questions:
-
Is there any way to guarantee that the system will stick to the telephoto lens under all conditions?
-
Is there any known reason the capture session would keep running but captureOutput() would stop firing?
I’d appreciate any guidance. If necessary, I can eventually put together a full project.
Thanks again,
Gal