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