diff --git a/web-ui/html/k2-app.html b/web-ui/html/k2-app.html index 318976b..de60b84 100644 --- a/web-ui/html/k2-app.html +++ b/web-ui/html/k2-app.html @@ -91,22 +91,33 @@ function connectCam(){ setStatus('끊김'); } }; + // 원래는 ev.candidate===null(ICE gathering 완전 종료)까지 기다린 뒤에만 오퍼를 + // 보냈는데, 이 네트워크에서 그 완전종료 신호가 20~30초씩 걸림(가상/VPN 인터페이스들을 + // 하나씩 훑고 타임아웃되는 걸로 보임) — 정작 이 프린터가 필요로 하는 STUN srflx + // 후보 자체는 보통 1~2초 안에 잡힘. 그래서 완전종료를 기다리지 않고 짧은 타임아웃 + // 뒤에 그때까지 모인 후보로 바로 보낸다(2026-09-19, 20초+ 연결지연 확인 후 수정). + let offerSent=false; + function sendOfferOnce(){ + if(offerSent || pc!==camPC) return; + offerSent=true; + fetch('/api/k2/webrtc-signal',{ + method:'POST', + headers:{'Content-Type':'text/plain', ...authH()}, + body:btoa(JSON.stringify({type:'offer',sdp:pc.localDescription.sdp})), + }).then(r=>r.text()).then(txt=>{ + const res=JSON.parse(atob(txt)); + if(res.type==='answer' && pc===camPC){ + pc.setRemoteDescription(new RTCSessionDescription(res)); + } + }).catch(()=>{ if(pc===camPC) setStatus('연결 실패'); }); + } pc.onicecandidate=(ev)=>{ - if(ev.candidate===null){ - fetch('/api/k2/webrtc-signal',{ - method:'POST', - headers:{'Content-Type':'text/plain', ...authH()}, - body:btoa(JSON.stringify({type:'offer',sdp:pc.localDescription.sdp})), - }).then(r=>r.text()).then(txt=>{ - const res=JSON.parse(atob(txt)); - if(res.type==='answer' && pc===camPC){ - pc.setRemoteDescription(new RTCSessionDescription(res)); - } - }).catch(()=>{ if(pc===camPC) setStatus('연결 실패'); }); - } + if(ev.candidate===null) sendOfferOnce(); // 빨리 끝나면 그대로 씀 }; pc.addTransceiver('video',{direction:'sendrecv'}); - pc.createOffer().then(d=>pc.setLocalDescription(d)).catch(()=>{ if(pc===camPC) setStatus('연결 실패'); }); + pc.createOffer().then(d=>pc.setLocalDescription(d)).then(()=>{ + setTimeout(sendOfferOnce, 1500); // 안 끝나면 1.5초 뒤 그때까지 모인 후보로 진행 + }).catch(()=>{ if(pc===camPC) setStatus('연결 실패'); }); } function reconnectCam(){ connectCam(); } connectCam();