[object Object]

← 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

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 →