zebra-audio: auto-reconnect on partner return + verifiable path
Reconnect: on "partner left", tear down the stale peer connection but stay in the room, and have whichever peer is already present send the offer when the other (re)joins — so a partner can leave and rejoin with no refresh, regardless of who left. Every (re)negotiation runs on a fresh RTCPeerConnection. Path indicator now reads the transport's selected candidate pair and prints the actual local/remote IP:port + types, so DIRECT vs RELAYED is verifiable (host = the two devices' IPs; relay = the TURN server's IP). The mic un-masks the real host candidate, which is why a voice call reaches direct P2P on a LAN where the no-mic chat fell back to TURN.
This commit is contained in:
parent
860afbd9e4
commit
8dfdd9561c
1 changed files with 32 additions and 12 deletions
|
|
@ -225,21 +225,27 @@ async function ensurePC(){
|
|||
async function reportPath(){
|
||||
if (!pc) return;
|
||||
try {
|
||||
const stats = await pc.getStats(); let pair=null;
|
||||
stats.forEach(r=>{ if(r.type==='candidate-pair'&&r.nominated&&r.state==='succeeded') pair=r; });
|
||||
if (!pair) stats.forEach(r=>{ if(!pair&&r.type==='candidate-pair'&&r.state==='succeeded') pair=r; });
|
||||
if (!pair){ setPath('path: connecting…', null); return; }
|
||||
const loc=stats.get(pair.localCandidateId)||{}, rem=stats.get(pair.remoteCandidateId)||{};
|
||||
if (loc.candidateType==='relay'||rem.candidateType==='relay')
|
||||
setPath('path: RELAYED via TURN (a server forwards your encrypted audio)', 'err');
|
||||
else
|
||||
setPath('path: DIRECT peer-to-peer — nobody between you ('+(loc.candidateType||'?')+'/'+(rem.candidateType||'?')+')', 'ok');
|
||||
const stats = await pc.getStats(); let sel=null;
|
||||
/* the authoritative active pair, per the transport */
|
||||
stats.forEach(r=>{ if(r.type==='transport'&&r.selectedCandidatePairId) sel=stats.get(r.selectedCandidatePairId); });
|
||||
if (!sel) stats.forEach(r=>{ if(r.type==='candidate-pair'&&r.state==='succeeded'&&(r.nominated||r.selected)) sel=r; });
|
||||
if (!sel){ setPath('path: connecting…', null); return; }
|
||||
const loc=stats.get(sel.localCandidateId)||{}, rem=stats.get(sel.remoteCandidateId)||{};
|
||||
const la=(loc.address||loc.ip||'?')+':'+(loc.port||'?'), ra=(rem.address||rem.ip||'?')+':'+(rem.port||'?');
|
||||
const relayed = loc.candidateType==='relay'||rem.candidateType==='relay';
|
||||
/* show the real endpoints so it's verifiable: host = the two devices' IPs;
|
||||
* relay = the TURN server's IP (142.93.73.64) appears on at least one side. */
|
||||
setPath('path: '+(relayed?'RELAYED via TURN':'DIRECT P2P')+' — you '+la
|
||||
+' ↔ peer '+ra+' ('+(loc.candidateType||'?')+'/'+(rem.candidateType||'?')+')', relayed?'err':'ok');
|
||||
logLine('', 'active pair: '+la+' ['+(loc.candidateType||'?')+'] ↔ '+ra+' ['+(rem.candidateType||'?')+']');
|
||||
} catch(_){}
|
||||
}
|
||||
|
||||
function relaySend(obj){ if (relayWS && relayWS.readyState===1) relayWS.send(JSON.stringify(obj)); }
|
||||
async function relaySendSDP(kind, desc){ relaySend({ type:'sdp', kind, data: b64(await aesEncrypt(relayKey, JSON.stringify(desc))) }); }
|
||||
function freshPC(){ if (pc){ try{pc.close();}catch(_){} pc=null; } }
|
||||
async function makeOffer(){
|
||||
freshPC(); /* always (re)negotiate on a clean connection */
|
||||
await ensurePC();
|
||||
await pc.setLocalDescription(await pc.createOffer());
|
||||
await waitForIceGathering(pc);
|
||||
|
|
@ -249,6 +255,7 @@ async function makeOffer(){
|
|||
async function onSDP(kind, json){
|
||||
const desc = JSON.parse(json);
|
||||
if (kind==='offer'){
|
||||
freshPC(); /* every incoming offer = a fresh connection */
|
||||
await ensurePC();
|
||||
await pc.setRemoteDescription(desc);
|
||||
await pc.setLocalDescription(await pc.createAnswer());
|
||||
|
|
@ -277,9 +284,22 @@ async function startCall(){
|
|||
relayWS.onerror = () => setStatus('rendezvous error','err');
|
||||
relayWS.onmessage = async (ev) => {
|
||||
let m; try { m=JSON.parse(ev.data); } catch(_){ return; }
|
||||
if (m.type==='welcome'){ relayRole=m.role; logLine('','role: '+m.role+' ('+m.peers+' present)'); if (relayRole==='offerer'&&m.peers>=2) await makeOffer(); }
|
||||
else if (m.type==='peer-joined'){ logLine('','partner joined'); if (relayRole==='offerer') await makeOffer(); }
|
||||
else if (m.type==='peer-left'){ logLine('','partner left'); setStatus('partner left', null); }
|
||||
/* present-member-offers: whoever is already in the room offers when the
|
||||
* other (re)joins; the joiner waits. Works for the first call and for any
|
||||
* reconnect, regardless of who left. */
|
||||
if (m.type==='welcome'){
|
||||
logLine('','joined room ('+m.peers+' present)');
|
||||
setStatus(m.peers>=2 ? 'partner here — connecting' : 'waiting for partner', null);
|
||||
}
|
||||
else if (m.type==='peer-joined'){ logLine('','partner joined — calling'); await makeOffer(); }
|
||||
else if (m.type==='peer-left'){
|
||||
logLine('','partner left — staying in room, will reconnect when they return');
|
||||
freshPC();
|
||||
$('remote-audio').srcObject=null; $('meter-rem').style.width='0%';
|
||||
$('dot-call').className='dot warn'; setPath('path: —', null);
|
||||
$('btn-mute').disabled=true; muted=false; $('btn-mute').textContent='mute'; $('btn-mute').className='';
|
||||
setStatus('partner left — waiting for them to return', null);
|
||||
}
|
||||
else if (m.type==='sdp'){ try { await onSDP(m.kind, await aesDecrypt(relayKey, unb64(m.data))); } catch(e){ logLine('err','sdp failed (codes must match): '+e.message); } }
|
||||
else if (m.type==='error'){ setStatus('rendezvous: '+m.message,'err'); }
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue