zebra-report: deploy realtime telemetry
This commit is contained in:
parent
5fc18d2423
commit
382d72b687
1 changed files with 103 additions and 4 deletions
|
|
@ -1640,6 +1640,7 @@ const sfuStreamsByPubHex = new Map(); // pubHex -> MediaStream
|
|||
|
||||
function attachSfuTrack(uuid, stream){
|
||||
let a = remoteAudio.get(uuid);
|
||||
const fresh = !a;
|
||||
if (!a){
|
||||
/* lease from the pre-blessed audio pool so Firefox Android's
|
||||
* per-element autoplay grant carries over from the entry-button
|
||||
|
|
@ -1650,12 +1651,26 @@ function attachSfuTrack(uuid, stream){
|
|||
a = leaseAudioElement();
|
||||
remoteAudio.set(uuid, a);
|
||||
applySinkTo(a);
|
||||
/* one-shot breadcrumbs so we can see whether the new path lights
|
||||
* up at all, vs. the silent-but-state=connected pattern. */
|
||||
a.addEventListener('playing', () => logLine('', 'rtc playing '+uuid.slice(0,4)+' ct='+a.currentTime.toFixed(2)), { once: true });
|
||||
a.addEventListener('pause', () => logLine('err','rtc paused '+uuid.slice(0,4)+' ct='+a.currentTime.toFixed(2)));
|
||||
a.addEventListener('ended', () => logLine('err','rtc ended '+uuid.slice(0,4)));
|
||||
a.addEventListener('stalled', () => logLine('err','rtc stalled '+uuid.slice(0,4)));
|
||||
a.addEventListener('error', () => logLine('err','rtc error '+uuid.slice(0,4)+' code='+(a.error?a.error.code:'?')));
|
||||
}
|
||||
a.srcObject = stream;
|
||||
const tracks = stream && stream.getAudioTracks ? stream.getAudioTracks() : [];
|
||||
const t0 = tracks[0];
|
||||
logLine('', 'sfu attach '+uuid.slice(0,4)+' fresh='+(fresh?1:0)+
|
||||
' tracks='+tracks.length+
|
||||
(t0 ? ' tr0={en='+t0.enabled+' mu='+t0.muted+' rs='+t0.readyState+'}' : ''));
|
||||
try {
|
||||
const p = a.play();
|
||||
if (p && p.catch) p.catch(e => logLine('err','rtc autoplay '+uuid+': '+e.message));
|
||||
} catch(_){}
|
||||
if (p && p.catch) p.catch(e => logLine('err','rtc autoplay '+uuid.slice(0,4)+': '+e.message));
|
||||
} catch(e){
|
||||
logLine('err','rtc play threw '+uuid.slice(0,4)+': '+e.message);
|
||||
}
|
||||
stopMeter(uuid); startMeter(uuid, stream);
|
||||
logLine('', 'sfu: receiving '+((members.get(uuid)||{}).handle || uuid));
|
||||
}
|
||||
|
|
@ -3355,6 +3370,88 @@ function stopAlivePings(){
|
|||
clearInterval(aliveTimer); aliveTimer = null;
|
||||
}
|
||||
|
||||
/* Telemetry tick — fired every TELEMETRY_INTERVAL_MS while in a room.
|
||||
* Dumps PC states + receiver RTP counters + <audio> element state to
|
||||
* the page log so we can see in real time whether a "silent" listener
|
||||
* is starved (no RTP arriving), decoded-but-not-playing (RTP fine but
|
||||
* <audio> paused/muted), or DJ-mode-stalled (streamAudio not loading).
|
||||
*
|
||||
* One line per tick per device gives a timeline that we can scroll
|
||||
* back through after a regression and pinpoint exactly when the chain
|
||||
* broke. Verbose by design — fox: "way more telemetry NOW".
|
||||
*
|
||||
* Numbers we lean on:
|
||||
* - sub.aud.pkt : packetsReceived on sub PC's audio receiver
|
||||
* - sub.aud.bytes : bytesReceived on the same
|
||||
* - sub.aud.jitter : current jitter in seconds
|
||||
* - sub.aud.level : audioLevel (0..1, 0 = silence)
|
||||
* - rtc[id]/stream[id] : <audio> element diagnostics
|
||||
* - ct = currentTime (advancing = audio playing)
|
||||
* - rs = readyState (4 = enough data)
|
||||
* - ns = networkState (1=idle 2=loading 3=stalled)
|
||||
* - pa = paused, mu = muted, vol = volume, err = MediaError code
|
||||
*/
|
||||
const TELEMETRY_INTERVAL_MS = 5000;
|
||||
let telemetryTimer = null;
|
||||
async function dumpTelemetry(){
|
||||
const parts = [];
|
||||
parts.push('role=' + myRole);
|
||||
parts.push('sub=' + (sfuSubPC ? sfuSubPC.connectionState + '/' + sfuSubPC.iceConnectionState : 'none'));
|
||||
parts.push('pub=' + (sfuPubPC ? sfuPubPC.connectionState + '/' + sfuPubPC.iceConnectionState : 'none'));
|
||||
parts.push('mesh=' + peers.size);
|
||||
parts.push('sListen=' + (selfListenerMode ? '1' : '0'));
|
||||
parts.push('streamMode=' + streamMode.size);
|
||||
parts.push('muted=' + (muted ? '1' : '0'));
|
||||
/* receiver stats from sfuSubPC (the main listener path) */
|
||||
if (sfuSubPC && typeof sfuSubPC.getStats === 'function'){
|
||||
try {
|
||||
const stats = await sfuSubPC.getStats(null);
|
||||
stats.forEach(r => {
|
||||
if (r.type === 'inbound-rtp' && r.kind === 'audio'){
|
||||
parts.push('aud.recv pkt=' + (r.packetsReceived|0) + ' lost=' + (r.packetsLost|0) +
|
||||
' bytes=' + (r.bytesReceived|0) + ' jitter=' + (r.jitter || 0).toFixed(4) +
|
||||
' level=' + (r.audioLevel || 0).toFixed(3));
|
||||
}
|
||||
if (r.type === 'inbound-rtp' && r.kind === 'video'){
|
||||
parts.push('vid.recv pkt=' + (r.packetsReceived|0) + ' lost=' + (r.packetsLost|0) +
|
||||
' frames=' + (r.framesDecoded|0));
|
||||
}
|
||||
});
|
||||
} catch(e){ parts.push('stats.err=' + e.message); }
|
||||
}
|
||||
/* every <audio> element in the room — both the WebRTC remoteAudio
|
||||
* pool (rtc[]) and the DJ HTTP-pull streamAudio (stream[]). */
|
||||
let i = 0;
|
||||
for (const [uuid, a] of remoteAudio){
|
||||
parts.push('rtc[' + uuid.slice(0,4) + '] rs=' + a.readyState + ' ns=' + a.networkState +
|
||||
' pa=' + (a.paused?1:0) + ' mu=' + (a.muted?1:0) + ' ct=' + a.currentTime.toFixed(2) +
|
||||
' err=' + (a.error?a.error.code:'_'));
|
||||
if (++i > 4) { parts.push('rtc…+' + (remoteAudio.size - i) + ' more'); break; }
|
||||
}
|
||||
i = 0;
|
||||
for (const [uuid, a] of streamAudio){
|
||||
parts.push('stream[' + uuid.slice(0,4) + '] rs=' + a.readyState + ' ns=' + a.networkState +
|
||||
' pa=' + (a.paused?1:0) + ' mu=' + (a.muted?1:0) + ' ct=' + a.currentTime.toFixed(2) +
|
||||
' src=' + (a.src ? (a.src.length>30 ? '…'+a.src.slice(-30) : a.src) : 'none') +
|
||||
' err=' + (a.error?a.error.code:'_'));
|
||||
if (++i > 4) { parts.push('stream…+' + (streamAudio.size - i) + ' more'); break; }
|
||||
}
|
||||
logLine('', '· ' + parts.join(' '));
|
||||
}
|
||||
function startTelemetryLoop(){
|
||||
if (telemetryTimer) return;
|
||||
telemetryTimer = setInterval(() => {
|
||||
dumpTelemetry().catch(e => logLine('err','telemetry: '+e.message));
|
||||
}, TELEMETRY_INTERVAL_MS);
|
||||
/* fire one immediately so the user sees state without waiting for
|
||||
* the first interval to elapse. */
|
||||
dumpTelemetry().catch(()=>{});
|
||||
}
|
||||
function stopTelemetryLoop(){
|
||||
if (!telemetryTimer) return;
|
||||
clearInterval(telemetryTimer); telemetryTimer = null;
|
||||
}
|
||||
|
||||
function openSignal(){
|
||||
ws = new WebSocket(SIGNAL_URL + '?room=' + encodeURIComponent(roomID));
|
||||
ws.onopen = async () => {
|
||||
|
|
@ -3364,10 +3461,12 @@ function openSignal(){
|
|||
const sig = await signBytes(sigJoin(roomID, nonce, myKeys.pubB64, myHandle));
|
||||
send({ type:'join', pubkey: myKeys.pubB64, handle: myHandle, nonce, sig });
|
||||
startAlivePings();
|
||||
startTelemetryLoop();
|
||||
};
|
||||
ws.onclose = () => {
|
||||
ws = null;
|
||||
stopAlivePings();
|
||||
stopTelemetryLoop();
|
||||
if (wantConnected){
|
||||
setStatus('rendezvous dropped — reconnecting…');
|
||||
if (sigReconnect) clearTimeout(sigReconnect);
|
||||
|
|
@ -5051,8 +5150,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
|
|||
|
||||
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-04</span><br>
|
||||
md5 <span class="stamp-md5">0136a83ef341ade811b60cfc6466d357</span><br>
|
||||
sha256 <span class="stamp-sha">0ab8c94acd78b1950385cd1abd93aa9bc7f3614dc08b3feddaa6dd9a8b7a746e</span><br>
|
||||
md5 <span class="stamp-md5">dbb0b8148bf501d93478f23305ede777</span><br>
|
||||
sha256 <span class="stamp-sha">e75528626e808db2c85749679e01ac45a3f308666d7fe606c370b5672d56201f</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue