I found a minimal yet effective workaround that now enables seamless camera switching across both IOS & Android platforms.
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.
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?
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 deviceId
s.
On iOS, we toggle the facingMode
between "user"
(front) and "environment"
(rear) using a simple count
variable.
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);
})();
✅ Works on iOS Safari and Chrome (tested on iPhone)
✅ Works on Android Chrome and Firefox
✅ No external libraries
✅ Graceful fallback for single-camera devices
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 ProfileJoin with Priyangsu’s personal invite link.
0
7
0