diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/transport.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/transport.js | 28 |
1 files changed, 27 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index 1a63e23..853ae75 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -44,6 +44,11 @@ const UPLOAD_BUFFER_HIGH = 1024 * 1024; // Segments of 256 KB: 24 in flight is 6 MB, enough to keep playback fed over a // slow link and small enough that nothing accumulates. +// How long to collect ICE candidates before sending the offer anyway. Long +// enough for a STUN round trip on a slow link, short enough that a STUN server +// that never answers costs a pause rather than the whole attempt. +const ICE_GATHER_TIMEOUT_MS = 4000; + const STREAM_CREDITS = 24; function _aborted() { @@ -155,10 +160,31 @@ class MeshBayTransport { const offer = await this._pc.createOffer(); await this._pc.setLocalDescription(offer); + // Wait for candidates, but not indefinitely. + // + // This is non-trickle signaling: the offer carries its candidates, so the + // SDP is only sent once gathering is done. When gathering *never* finishes + // — a STUN server that is slow, filtered, or being resolved through a DNS + // that is not answering — this promise never settles, and joining a group + // hangs with no error and nothing on screen. Reported after exactly that, + // and it succeeded on a later attempt, which is the shape of a network + // wait rather than a refusal. + // + // Past the deadline the offer goes out with whatever has been gathered. + // Host candidates are already there, which is enough on a LAN — the case + // this project cares most about — and the reflexive ones normally arrive + // in well under a second when STUN is reachable at all. A partial offer + // that usually connects beats a promise that never returns. await new Promise((resolve) => { if (this._pc.iceGatheringState === 'complete') return resolve(); + const done = () => { clearTimeout(timer); resolve(); }; + const timer = setTimeout(() => { + console.warn('[MeshBay] ICE gathering did not finish in', + ICE_GATHER_TIMEOUT_MS, 'ms — offering what we have'); + done(); + }, ICE_GATHER_TIMEOUT_MS); this._pc.onicegatheringstatechange = () => { - if (this._pc.iceGatheringState === 'complete') resolve(); + if (this._pc.iceGatheringState === 'complete') done(); }; }); |