Parcourir la source

Mobile screen share bug fix

Sravan il y a 1 semaine
Parent
révision
1d9ffcc3d4
1 fichiers modifiés avec 28 ajouts et 11 suppressions
  1. 28
    11
      server/public/share.html

+ 28
- 11
server/public/share.html Voir le fichier

@@ -20,7 +20,7 @@
20 20
   .sub{color:var(--muted);font-size:.97rem;line-height:1.5;margin-bottom:1.6rem;}
21 21
   .codewrap{background:#fffdf2;border:2px dashed var(--brand);border-radius:14px;padding:1.2rem;}
22 22
   .codelabel{font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);margin-bottom:.3rem;}
23
-  .code{font-size:3rem;letter-spacing:.5rem;font-weight:800;color:var(--ink);}
23
+  .code{font-size:clamp(1.9rem,12vw,3rem);letter-spacing:clamp(.18rem,3vw,.5rem);font-weight:800;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:clip;}
24 24
   .status{margin-top:1.3rem;padding:.7rem 1rem;border-radius:10px;background:#f1f5f9;color:#475569;font-size:.92rem;}
25 25
   .status.on{background:#ecfdf3;color:#15803d;}
26 26
   .consent{margin-top:1.3rem;border:1px solid #c7d6f0;background:var(--blue-soft);border-left:5px solid var(--blue);border-radius:12px;padding:1.3rem;text-align:left;color:var(--blue-d);}
@@ -117,23 +117,40 @@ function showConsent(m){
117 117
   const name=(m.technician&&m.technician.trim())?m.technician:'Your support agent';
118 118
   consentBox.innerHTML='<div class="consent">Your support agent <span class="who">'+esc(name)+'</span> would like to view your screen to help you.'+
119 119
     '<div class="btns"><button class="grant" id="g">Allow</button><button class="deny" id="d">Not now</button></div></div>';
120
-  const allow=()=>{consentBox.innerHTML='';document.removeEventListener('keydown',onKey);ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:true}));};
120
+  const allow=async()=>{
121
+    document.removeEventListener('keydown',onKey);
122
+    setStatus('Opening the screen picker — choose your screen and tap Share / Start.','on');
123
+    const ok=await beginCapture();
124
+    if(!ok){ consentBox.innerHTML=''; try{ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:false}));}catch(_){} setStatus('Screen share was cancelled. Refresh this page if you need a new code.'); return; }
125
+    consentBox.innerHTML='';
126
+    try{ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:true}));}catch(_){}
127
+  };
121 128
   const onKey=(e)=>{if(e.key==='Enter'){e.preventDefault();allow();}};
122 129
   document.addEventListener('keydown',onKey);
123 130
   document.getElementById('g').onclick=allow;
124 131
   document.getElementById('d').onclick=()=>{consentBox.innerHTML='';document.removeEventListener('keydown',onKey);ws.send(JSON.stringify({type:'consent',sessionId:m.sessionId,granted:false}));setStatus('Connection declined. Refresh this page if you need a new code.');};
125 132
 }
133
+// Capture the screen (+mic) DIRECTLY from the Allow tap. Mobile browsers reject
134
+// getDisplayMedia unless it is called from a user gesture, so this must not run
135
+// after a server round-trip. getDisplayMedia is called first to keep the gesture.
136
+async function beginCapture(){
137
+  try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:'monitor',frameRate:{ideal:30}},audio:false,monitorTypeSurfaces:'include'}); }
138
+  catch(err){ return false; }
139
+  try{ const mic=await navigator.mediaDevices.getUserMedia({audio:true}); window.__mic=mic; mic.getAudioTracks().forEach(t=>localStream.addTrack(t)); }catch(e){}
140
+  try{ ensureIce(); }catch(_){}
141
+  return true;
142
+}
126 143
 async function startStreaming(){
127
-  // Make sure TURN/ICE is loaded before building the connection (needed for mobile/cellular relay).
144
+  // If the Allow tap already captured the screen (mobile path), reuse it.
145
+  if(!localStream){
146
+    await ensureIce();
147
+    let mic=null; try{ mic=await navigator.mediaDevices.getUserMedia({audio:true}); }catch(e){ mic=null; }
148
+    setStatus('In the popup: choose your screen, then tap Share / Start.','on');
149
+    try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:'monitor',frameRate:{ideal:30}},audio:false,monitorTypeSurfaces:'include'}); }
150
+    catch(err){ if(mic){try{mic.getTracks().forEach(t=>t.stop());}catch(_){}} try{ws.send(JSON.stringify({type:'end-session',sessionId,reason:'share-cancelled'}));}catch(e){} setStatus('Screen share was cancelled. Refresh the page to try again.'); return; }
151
+    if(mic){ window.__mic=mic; try{mic.getAudioTracks().forEach(t=>localStream.addTrack(t));}catch(_){} }
152
+  }
128 153
   await ensureIce();
129
-  // Ask for the microphone FIRST. On Android the screen capture must be the LAST
130
-  // capture started, otherwise the mic permission prompt interrupts it and the share ends.
131
-  let mic=null;
132
-  try{ mic=await navigator.mediaDevices.getUserMedia({audio:true}); }catch(e){ mic=null; }
133
-  setStatus('In the popup: choose your screen, then tap Share / Start.','on');
134
-  try{ localStream=await navigator.mediaDevices.getDisplayMedia({video:{frameRate:{ideal:30}},audio:false}); }
135
-  catch(err){ if(mic){try{mic.getTracks().forEach(t=>t.stop());}catch(_){}} try{ws.send(JSON.stringify({type:'end-session',sessionId,reason:'share-cancelled'}));}catch(e){} setStatus('Screen share was cancelled. Refresh the page to try again.'); return; }
136
-  if(mic){ window.__mic=mic; try{mic.getAudioTracks().forEach(t=>localStream.addTrack(t));}catch(_){} }
137 154
   indicator.classList.add('show'); setStatus('You are now sharing your screen with your agent.','on');
138 155
   { const hl=document.getElementById('homeLink'); if(hl) hl.style.display='none'; }
139 156
   window.onbeforeunload=function(){ if((localStream||document.getElementById('sessionBar'))&&!sessionOver){ return 'Leaving or refreshing this page will end your screen sharing session.'; } };

Chargement…
Annuler
Enregistrer