Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent iOS from Switching Between Back Camera Lenses in getUserMedia (Safari/WebView, iOS 18)

I’m developing a hybrid app (WebView / Turbo Native) that uses getUserMedia to access the back camera for a PPG/heart rate measurement feature (the user places their finger on the camera).

Problem: Even when I specify constraints like:

{
  video: {
    deviceId: '...',
    facingMode: { exact: 'environment' },
    advanced: [{ zoom: 1.0 }]
  },
  audio: false
}

On iPhone 15 (iOS 18), iOS unexpectedly switches between the wide, ultra-wide, and telephoto lenses during the measurement.

This breaks the heart rate detection, and it forces the user to move their finger in the middle of the measurement.

Question: Is there any way, via getUserMedia/WebRTC, to force iOS to use only the wide-angle lens and prevent automatic lens switching?

I know that with AVFoundation (Swift) you can pick .builtInWideAngleCamera, but I’m hoping to avoid building a custom native layer and would prefer to stick with WebView/JavaScript if possible to save time and complexity.

Any suggestions, workarounds, or updates from Apple would be greatly appreciated!

Thanks a lot!

like image 292
gal Avatar asked Oct 17 '25 13:10

gal


2 Answers

iPhones with multiple rear cameras (like 11peo) automatically switch between lenses based on lighting conditions and focus distance, even when facingMode: { exact: "environment" } is set. Unfortunately, getUserMedia in WebView doesn’t provide full control over which specific rear camera (wide, ultra-wide, or telephoto) is used.

Instead of you to rely on facingMode, you can try listing available cameras and selecting the one that matches the wide-angle lens.

You can achieve that by:

async function getWideAngleCamera() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  const backCameras = devices.filter(device => device.kind === "videoinput" && device.label.toLowerCase().includes("back"));
  
  //pick the camera that has 'wide' in the label
  const wideCamera = backCameras.find(device => device.label.toLowerCase().includes("wide")) || backCameras[0];

  if (!wideCamera) {
    console.error("No suitable wide-angle camera found.");
    return null;
  }

  return wideCamera.deviceId;
}

async function startCamera() {
  const deviceId = await getWideAngleCamera();

  if (!deviceId) return;

  const stream = await navigator.mediaDevices.getUserMedia({
    video: {
      deviceId: { exact: deviceId },
      advanced: [{ zoom: 1.0 }]
    }
  });

  document.querySelector("video").srcObject = stream;
}

startCamera();

like image 75
Hilory Avatar answered Oct 19 '25 03:10

Hilory


I recommend to use the ultra-wide angle lens for your use case. because the finger is very close from lenses so the camera will switch to Macro mode automatically

It is fairly easy, if you set the zoom to 0.5 it will keep using the ultra-wide angle lens no matter what.

Alternatively you can use exact with device ID

{
  video: {
    deviceId: { exact: '...'},
    facingMode: { exact: 'environment' },
    advanced: [{ zoom: 1.0 }]
  },
  audio: false
}

to require the specific camera, you would use:

getUserMedia({
 video: {
  deviceId: {
    exact: myExactCameraOrBustDeviceId,
  },
 },
});

See the documentation

like image 21
Jabbar Avatar answered Oct 19 '25 05:10

Jabbar