Priyangsu Banerjee

May 12, 2025 • 2 min read

Solving Camera Switch in Mobile Browsers: A Unified Approach for Android & iOS

I found a minimal yet effective workaround that now enables seamless camera switching across both IOS & Android platforms.

Solving Camera Switch in Mobile Browsers: A Unified Approach for Android & iOS

Switching cameras on a mobile web app should be simple, right? You fetch the list of video input devices, let the user toggle through them, and call getUserMedia() with the new device. That logic holds for Android. But if you're developing for iOS Safari, things break—badly.

After struggling with common issues like:

  • deviceId being "" (empty string),

  • enumerateDevices() returning unlabeled or unusable devices on iOS,

  • inability to programmatically switch between front and rear cameras.

🧠 The Problem

iOS browsers (especially Safari) don’t expose actual camera device IDs due to privacy restrictions, and often return empty label, deviceId, and groupId values. This makes typical device-based camera switching ineffective.

So, how do you reliably toggle cameras on both iOS and Android?

✅ The Solution

We apply conditional logic based on the platform:

  • On Android, we use navigator.mediaDevices.enumerateDevices() to fetch all available video input devices, and switch cameras by cycling through deviceIds.

  • On iOS, we toggle the facingMode between "user" (front) and "environment" (rear) using a simple count variable.

🔧 JavaScript Implementation

Here's the core JavaScript logic:

let currentStream = null;
let videoDevices = [];
let deviceIndex = 0;
let count = 2; // Used to toggle camera on iOS

const video = document.getElementById("video");
const switchBtn = document.getElementById("switchBtn");
const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);

async function startCamera(deviceId = null) {
  if (currentStream) {
    currentStream.getTracks().forEach((track) => track.stop());
  }

  const constraints = isIOS
    ? {
        video: {
          facingMode: count % 2 === 0 ? "environment" : "user",
        },
        audio: false,
      }
    : {
        video: deviceId ? { deviceId: { exact: deviceId } } : true,
        audio: false,
      };

  try {
    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    currentStream = stream;
    video.srcObject = stream;
    await video.play();
  } catch (err) {
    console.error("Camera error:", err);
    alert("Could not access the camera.");
  }
}

function stopCamera() {
  if (currentStream) {
    currentStream.getTracks().forEach((track) => track.stop());
    currentStream = null;
    video.srcObject = null;
  }
}

async function getVideoDevices() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  videoDevices = devices.filter((d) => d.kind === "videoinput");
}

switchBtn.addEventListener("click", async () => {
  if (videoDevices.length <= 1) {
    if (isIOS) {
      count++;
      await startCamera();
    } else {
      alert("No other camera available.");
    }
    return;
  }

  deviceIndex = (deviceIndex + 1) % videoDevices.length;
  await startCamera(videoDevices[deviceIndex].deviceId);
});

(async () => {
  await getVideoDevices();
  await startCamera(videoDevices[deviceIndex]?.deviceId);
})();

🚀 Benefits of This Method

  • ✅ Works on iOS Safari and Chrome (tested on iPhone)

  • ✅ Works on Android Chrome and Firefox

  • ✅ No external libraries

  • ✅ Graceful fallback for single-camera devices

Conclusion

This approach avoids the need for platform-specific hacks or native wrappers. It provides a clean, user-friendly camera toggle experience right in the browser — with minimal code.

If you're building a web app for photo capture, scanning, or video conferencing, this technique ensures consistent behavior across platforms.

Live example: https://priyangsubanerjee.github.io/geo-tagger/

#javascript #webcameraapi #webdevelopment #camera

Join Priyangsu on Peerlist!

Join amazing folks like Priyangsu and thousands of other people in tech.

Create Profile

Join with Priyangsu’s personal invite link.

0

7

0