← back to Dw Photo Capture
Camera-unavailable UX: detect Chrome/3rd-party iOS browsers (no getUserMedia) and tell user to open in Safari, instead of a cryptic error
8cff6c45eff86cfbdd5057177752574e4e109ac6 · 2026-07-06 21:36:02 -0700 · Steve Abrams
Files touched
M public/caps.htmlM public/index.html
Diff
commit 8cff6c45eff86cfbdd5057177752574e4e109ac6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 6 21:36:02 2026 -0700
Camera-unavailable UX: detect Chrome/3rd-party iOS browsers (no getUserMedia) and tell user to open in Safari, instead of a cryptic error
---
public/caps.html | 4 ++++
public/index.html | 7 +++++--
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/public/caps.html b/public/caps.html
index 6c1ab69..af9845f 100644
--- a/public/caps.html
+++ b/public/caps.html
@@ -23,6 +23,10 @@ function row(k,ok,val){ return `<div class="row"><span class="k">${k}</span><spa
const rep={ua:navigator.userAgent, motion:typeof DeviceMotionEvent!=='undefined',
motionPerm:(typeof DeviceMotionEvent!=='undefined' && typeof DeviceMotionEvent.requestPermission==='function')};
try{
+ if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia){
+ const chrome=/CriOS|FxiOS|EdgiOS/.test(navigator.userAgent);
+ throw new Error(chrome?'This is Chrome/other app — iOS only allows the camera in SAFARI. Open in Safari.':'camera API unavailable (open over https in Safari)');
+ }
const st=await navigator.mediaDevices.getUserMedia({video:{facingMode:{ideal:'environment'}}});
$('#v').srcObject=st; const tr=st.getVideoTracks()[0];
const caps=tr.getCapabilities?tr.getCapabilities():{}, set=tr.getSettings?tr.getSettings():{};
diff --git a/public/index.html b/public/index.html
index f25bf7c..d6f3dfb 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1626,8 +1626,11 @@ function mMakeBox(el){ let mode=null,sx,sy,ox,oy,ow,oh; const rz=el.querySelecto
rz.addEventListener('pointerdown',e=>start(e,'resize'));
el.addEventListener('pointermove',move); rz.addEventListener('pointermove',move);
el.addEventListener('pointerup',end); rz.addEventListener('pointerup',end); }
-async function openMeasure(){ try{ _mStream=await navigator.mediaDevices.getUserMedia({video:{facingMode:{ideal:'environment'}}}); }
- catch(e){ toast('Camera unavailable for measuring'); return; }
+async function openMeasure(){
+ if(!navigator.mediaDevices||!navigator.mediaDevices.getUserMedia){
+ toast('📷 Live camera needs Safari on iPhone — Chrome/other apps block it. Open in Safari.'); return; }
+ try{ _mStream=await navigator.mediaDevices.getUserMedia({video:{facingMode:{ideal:'environment'}}}); }
+ catch(e){ toast('Camera blocked — allow camera access, or open in Safari.'); return; }
// LOCK ZOOM to minimum so the focal length / field-of-view stays constant (scale depends on it)
try{ const tr=_mStream.getVideoTracks()[0], caps=tr.getCapabilities?tr.getCapabilities():{};
if(caps.zoom) await tr.applyConstraints({advanced:[{zoom:caps.zoom.min||1}]}); }catch(e){}
← b0a3ecc Handoff doc: how to wire the fine-tuned DW CLIP weights into
·
back to Dw Photo Capture
·
Calibrate-once measurement: card sizes px/in for the device a2500f8 →