Files
mirotalk/public/js/networkStats.js
T
2025-11-21 08:16:20 +01:00

144 lines
4.0 KiB
JavaScript

'use strict';
/*
Fully correct WebRTC network stats collector.
- Supports audio + video
- Correct RTT (remote-inbound-rtp)
- Correct jitter (inbound-rtp)
- Multi-peer aggregation
*/
const networkSent = document.getElementById('networkSent');
const networkReceived = document.getElementById('networkReceived');
const networkJitter = document.getElementById('networkJitter');
const networkPacketLost = document.getElementById('networkPacketLost');
const networkRtt = document.getElementById('networkRtt');
const statsInterval = 3000; // every 3 seconds
/**
* Read network stats for a single RTCPeerConnection.
* Works for both audio and video.
*/
async function getNetworkStats(pc) {
let bytesSent = 0;
let bytesReceived = 0;
let packetsLost = 0;
let jitterSum = 0;
let jitterCount = 0;
let rttSum = 0;
let rttCount = 0;
if (!pc) {
return { bytesSent, bytesReceived, packetsLost, jitter: 0, rtt: 0 };
}
const stats = await pc.getStats();
stats.forEach((report) => {
// Outbound: Anything we send (audio/video)
if (report.type === 'outbound-rtp') {
bytesSent += report.bytesSent || 0;
}
// Inbound: Anything we receive (audio/video)
if (report.type === 'inbound-rtp') {
bytesReceived += report.bytesReceived || 0;
packetsLost += report.packetsLost || 0;
if (report.jitter !== undefined) {
jitterSum += report.jitter;
jitterCount++;
}
}
// RTT only exists in remote-inbound-rtp
if (report.type === 'remote-inbound-rtp') {
if (report.roundTripTime !== undefined) {
rttSum += report.roundTripTime;
rttCount++;
}
}
});
return {
bytesSent,
bytesReceived,
packetsLost,
jitter: jitterCount ? jitterSum / jitterCount : 0,
rtt: rttCount ? rttSum / rttCount : 0,
};
}
/** Convert bytes to readable string */
function bytesToSize(bytes) {
if (bytes === 0) return '0 b';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
const sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb'];
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
}
/**
* Convert seconds to ms, s, h as readable string
*/
function timeToReadable(seconds) {
if (seconds < 1e-3) return (seconds * 1e6).toFixed(2) + ' μs';
if (seconds < 1) return (seconds * 1000).toFixed(2) + ' ms';
if (seconds < 60) return seconds.toFixed(3) + ' s';
if (seconds < 3600) return (seconds / 60).toFixed(2) + ' min';
return (seconds / 3600).toFixed(2) + ' h';
}
/** Display into UI */
function showNetworkStats(stats) {
networkSent.innerText = bytesToSize(stats.bytesSent);
networkReceived.innerText = bytesToSize(stats.bytesReceived);
networkJitter.innerText = timeToReadable(stats.jitter);
networkPacketLost.innerText = stats.packetsLost;
networkRtt.innerText = timeToReadable(stats.rtt);
}
/**
* Aggregate all peer connections
*/
setInterval(async () => {
let global = {
bytesSent: 0,
bytesReceived: 0,
packetsLost: 0,
jitter: 0,
rtt: 0,
};
let jitterCount = 0;
let rttCount = 0;
for (const pc of Object.values(peerConnections)) {
const s = await getNetworkStats(pc);
global.bytesSent += s.bytesSent;
global.bytesReceived += s.bytesReceived;
global.packetsLost += s.packetsLost;
if (s.jitter > 0) {
global.jitter += s.jitter;
jitterCount++;
}
if (s.rtt > 0) {
global.rtt += s.rtt;
rttCount++;
}
}
if (jitterCount > 0) global.jitter /= jitterCount;
if (rttCount > 0) global.rtt /= rttCount;
showNetworkStats({
bytesSent: global.bytesSent,
bytesReceived: global.bytesReceived,
packetsLost: global.packetsLost,
jitter: global.jitter,
rtt: global.rtt,
});
}, statsInterval);