improve WebRTC NAT reliability diagnostics
This commit is contained in:
1 parent
f3c735b808
commit
365415740e
5 files changed
+104
-11
No files matched your search
+78
-1
@@ -96,6 +96,41 @@ function toast(msg, kind = '') {
|
||||
setTimeout(() => t.remove(), 5000);
|
||||
}
|
||||
|
||||
const ICE_NOTICE_INTERVAL = 15000;
|
||||
function notifyIceIssue(peer, message, extra = {}) {
|
||||
const now = Date.now();
|
||||
const key = `${message}:${extra.url || ''}`;
|
||||
if (!peer || peer.lastIceNotice?.key !== key || now - peer.lastIceNotice.at > ICE_NOTICE_INTERVAL) {
|
||||
toast(`${peer?.name || 'Voice'}: ${message}`, 'warn');
|
||||
if (peer) peer.lastIceNotice = { key, at: now };
|
||||
}
|
||||
captureRumIssue(message, 'WARNING', {
|
||||
peerId: peer?.id,
|
||||
peerName: peer?.name,
|
||||
...extra,
|
||||
});
|
||||
}
|
||||
|
||||
async function selectedIcePath(pc) {
|
||||
if (!pc?.getStats) return null;
|
||||
const stats = await pc.getStats();
|
||||
let pair;
|
||||
stats.forEach((report) => {
|
||||
if (report.type === 'candidate-pair' && (report.selected || report.nominated) && report.state === 'succeeded') {
|
||||
pair = report;
|
||||
}
|
||||
});
|
||||
if (!pair) return null;
|
||||
const local = stats.get(pair.localCandidateId);
|
||||
const remote = stats.get(pair.remoteCandidateId);
|
||||
return {
|
||||
localType: local?.candidateType,
|
||||
remoteType: remote?.candidateType,
|
||||
protocol: pair.protocol,
|
||||
path: local?.candidateType === 'relay' || remote?.candidateType === 'relay' ? 'TURN relay' : 'direct/STUN',
|
||||
};
|
||||
}
|
||||
|
||||
function playRetroCue(name) {
|
||||
const ctx = S.audioCtx;
|
||||
if (!ctx) return;
|
||||
@@ -524,6 +559,8 @@ function makePeerRecord(p) {
|
||||
pendingCandidates: [], micStream: null, screenStreams: new Map(),
|
||||
gain: null, panner: null, analyser: null, vol: 1, speaking: false,
|
||||
resetAttempts: 0,
|
||||
lastIceNotice: null,
|
||||
icePath: null,
|
||||
els: {},
|
||||
};
|
||||
}
|
||||
@@ -562,17 +599,40 @@ function ensurePC(peer, initiator) {
|
||||
if (candidate) send({ type: 'candidate', to: peer.id, candidate });
|
||||
};
|
||||
pc.onicecandidateerror = (event) => {
|
||||
captureRumIssue('WebRTC ICE candidate error', 'WARNING', {
|
||||
notifyIceIssue(peer, `ICE server unavailable (${event.errorCode || 'unknown'})`, {
|
||||
peerId: peer.id,
|
||||
iceConnectionState: pc.iceConnectionState,
|
||||
iceGatheringState: pc.iceGatheringState,
|
||||
errorCode: event.errorCode,
|
||||
errorText: event.errorText,
|
||||
url: event.url,
|
||||
});
|
||||
};
|
||||
pc.oniceconnectionstatechange = () => {
|
||||
const state = pc.iceConnectionState;
|
||||
if (state === 'disconnected') {
|
||||
notifyIceIssue(peer, 'ICE disconnected; audio may stop', {
|
||||
iceConnectionState: state,
|
||||
iceGatheringState: pc.iceGatheringState,
|
||||
});
|
||||
} else if (state === 'failed') {
|
||||
notifyIceIssue(peer, 'ICE failed; trying TURN relay', {
|
||||
iceConnectionState: state,
|
||||
iceGatheringState: pc.iceGatheringState,
|
||||
});
|
||||
}
|
||||
};
|
||||
pc.onconnectionstatechange = () => {
|
||||
renderTiles();
|
||||
if (pc.connectionState === 'failed') {
|
||||
console.warn('peer connection failed, rebuilding it from scratch', peer.id);
|
||||
notifyIceIssue(peer, 'connection failed; rebuilding voice path', {
|
||||
connectionState: pc.connectionState,
|
||||
iceConnectionState: pc.iceConnectionState,
|
||||
iceGatheringState: pc.iceGatheringState,
|
||||
signalingState: pc.signalingState,
|
||||
resetAttempts: peer.resetAttempts,
|
||||
});
|
||||
captureRumIssue('WebRTC peer connection failed', 'ERROR', {
|
||||
peerId: peer.id,
|
||||
state: pc.connectionState,
|
||||
@@ -581,6 +641,23 @@ function ensurePC(peer, initiator) {
|
||||
recreatePeerConnection(peer, { announce: true });
|
||||
} else if (pc.connectionState === 'connected') {
|
||||
peer.resetAttempts = 0; // a successful connection clears the failure count
|
||||
selectedIcePath(pc).then((path) => {
|
||||
if (!path) return;
|
||||
peer.icePath = path;
|
||||
toast(`${peer.name}: ${path.path} connection`, 'ok');
|
||||
captureRumIssue('WebRTC connection established', 'INFO', {
|
||||
peerId: peer.id,
|
||||
peerName: peer.name,
|
||||
connectionState: pc.connectionState,
|
||||
iceConnectionState: pc.iceConnectionState,
|
||||
...path,
|
||||
});
|
||||
}).catch((err) => {
|
||||
captureRumIssue('Could not inspect selected ICE path', 'WARNING', {
|
||||
peerId: peer.id,
|
||||
error: err?.message || String(err),
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
pc.ontrack = (ev) => onRemoteTrack(peer, ev);
|
||||
|
||||
Reference in new issue
Block a user