Files
archived-vdo.ninja/results.html
steveseguin 9c996318c0 Mbps to mbps
2025-06-06 01:14:28 -04:00

722 lines
22 KiB
HTML

<html>
<head>
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<meta charset="utf8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>VDON Speed Test</title>
<style>
:root {
--primary-bg: #333;
--secondary-bg: #444;
--card-bg: #3a3a3a;
--text-color: white;
--accent-color: #4296f5;
--accent-hover: #2979e2;
--success-color: #60ff60;
--warning-color: #f3f304;
--error-color: #fb4949;
--border-color: #666;
}
body {
text-align: center;
background: var(--primary-bg);
font-family: 'Noto Sans', sans-serif;
color: var(--text-color);
margin: 0;
padding: 0;
line-height: 1.6;
}
h1, h2, h3 {
margin-top: 1rem;
margin-bottom: 1rem;
}
h2 {
width: 760px;
max-width: 90%;
margin: auto;
}
a {
color: white;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
#mainapp {
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
}
#container {
background: var(--secondary-bg);
border-radius: 8px;
padding: 1.5rem;
margin: 1.5rem auto;
max-width: 900px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
}
#graphs {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1.5rem;
margin: 2rem auto;
max-width: 1100px;
}
.graph {
background: var(--card-bg);
border-radius: 8px;
padding: 0;
width: 300px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.graph h3 {
margin-top: 0;
margin-bottom: 0.5rem;
}
.graph span {
display: block;
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
canvas {
width: 100%;
height: 100px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
#details {
background: var(--secondary-bg);
border-radius: 8px;
padding: 1.5rem;
margin: 1.5rem auto;
max-width: 900px;
text-align: left;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
}
button {
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 4px;
padding: 10px 15px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: var(--accent-hover);
}
.hidden {
display: none !important;
}
#detailed-info {
margin-top: 30px;
padding: 20px;
border-top: 1px solid var(--border-color);
background: rgba(0, 0, 0, 0.1);
border-radius: 0 0 8px 8px;
}
.info-buttons {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
margin-top: 15px;
}
.info-buttons button {
background-color: var(--accent-color);
color: white;
border: none;
border-radius: 4px;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.info-buttons button:hover {
background-color: var(--accent-hover);
}
.success {
color: var(--success-color);
}
.warning {
color: var(--warning-color);
}
.error {
color: var(--error-color);
}
/* Blink animation for loading text */
@keyframes fadein {
0% {opacity: 0.5;}
100% {opacity: 1;}
}
blink {
animation: blink 1s step-end infinite;
}
@keyframes blink {
50% { opacity: 0; }
}
/* Make design responsive */
@media (max-width: 768px) {
#graphs {
flex-direction: column;
align-items: center;
}
.graph {
width: 90%;
}
}
</style>
</head>
<body>
<div id="mainapp" >
<h1>
Video and stream quality check results
</h1>
<div id="container">
<h3><blink><span style='color:#85c0ff'>RESULTS ARE LOADING...</span></blink><h3>
</div>
<div id="graphs">
<div class="graph">
<h3>Bitrate (kbps)</h3>
<span>0</span>
<canvas id="bitrate-graph"></canvas>
</div>
<div class="graph">
<h3>Buffer delay (ms)</h3>
<span>0</span>
<canvas id="buffer-graph"></canvas>
</div>
<div class="graph">
<h3>Packet Loss (%)</h3>
<span>0</span>
<canvas id="packetloss-graph"></canvas>
</div>
</div>
<div id="details">
</div>
</div>
<script>
function getChromiumVersion() {
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
return raw ? parseInt(raw[2], 10) : false;
}
function next1(){
document.getElementById("page1").classList.add("hidden");
document.getElementById("page2").classList.remove("hidden");
}
function next2(){
document.getElementById("page2").classList.add("hidden");
document.getElementById("page3").classList.remove("hidden");
loadIframe();
}
function next3(){
document.getElementById("page3").classList.add("hidden");
document.getElementById("mainapp").classList.remove("hidden");
loadIframe();
}
(function (w) {
w.URLSearchParams =
w.URLSearchParams ||
function (searchString) {
var self = this;
self.searchString = searchString;
self.get = function (name) {
var results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(
self.searchString
);
if (results == null) {
return null;
} else {
return decodeURI(results[1]) || 0;
}
};
};
})(window);
var urlParams = new URLSearchParams(window.location.search);
function copyFunction(copyText) {
alert("Log copied to the clipboard.");
try {
copyText.select();
copyText.setSelectionRange(0, 99999);
document.execCommand("copy");
} catch (e) {
var dummy = document.createElement("input");
document.body.appendChild(dummy);
dummy.value = copyText;
dummy.select();
document.execCommand("copy");
document.body.removeChild(dummy);
return false;
}
}
function timeConverter(UNIX_timestamp){
var a = new Date(UNIX_timestamp);
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
var year = a.getFullYear();
var month = months[a.getMonth()];
var date = a.getDate();
var hours = a.getHours();
var minutes = a.getMinutes();
var ampm = hours >= 12 ? 'pm' : 'am';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
minutes = minutes < 10 ? '0'+minutes : minutes;
var strTime = hours + ':' + minutes + ' ' + ampm;
var time = date + ' ' + month + ' ' + year + ' ' + hours + ':' + minutes + ampm;
return time;
}
function printValues(obj) {
var out = "";
for (var key in obj) {
if (typeof obj[key] === "object") {
out += "<br />";
out += printValues(obj[key]);
} else {
if (key.startsWith("_")) {
} else {
out += "<b>" + key + "</b>: " + obj[key] + "<br />";
}
}
}
return out;
}
var streamID = "";
if (urlParams.has("id")) {
streamID = urlParams.get("id");
}
var bitrate = {
element: "bitrate-graph",
data: 0,
max: 6000,
target: 2500,
};
var frames;
var buffer = {
element: "buffer-graph",
data: 0,
max: 200,
target: 100,
};
var packetloss = {
element: "packetloss-graph",
data: 0,
max: 3,
target: 2,
};
// https://record.vdo.workers.dev/?name="+recordResults
var QLR_1 = 0;
var QLR_2 = 0;
var QLR_3 = 0;
var BBB = 0;
var counter = 0;
var BUFF = 0;
var BUFFCCC = 0;
var PAK = 0;
var PAKCCC = 0;
var packetLossSpikes = 0;
function process(arr) {
var maxResolution = "0 x 0";
console.log(arr);
// Clear container
document.getElementById("container").innerHTML = "<h3>Quality Test Results</h3>";
arr.forEach(data => {
if ("bitrate" in data) {
updateData("bitrate", data.bitrate);
if (data.bitrate !== null) {
BBB += data.bitrate;
counter += 1;
}
}
if ("target" in data) {
updateData("target", data.target);
}
if ("buffer" in data) {
updateData("buffer", data.buffer);
if (data.buffer !== null) {
BUFF += data.buffer;
BUFFCCC += 1;
}
}
if (data.packetloss !== undefined && !isNaN(data.packetloss)) {
updateData("packetloss", data.packetloss);
if (data.packetloss !== null) {
PAK += parseFloat(data.packetloss) || 0;
PAKCCC += 1;
if (parseFloat(data.packetloss) > 2.0) {
packetLossSpikes++;
}
}
}
if (data.timestart) {
document.getElementById("details").innerHTML += "<br /><b>Test start time:</b> " + timeConverter(data.timestart) + "<br />";
}
if ("peakhour" in data){
if (!data.peakhour){
document.getElementById("details").innerHTML += "<small><i>(This test was completed outside of peak streaming hours.</br>Results are normally worse during peak hours.)</i></small><br />";
}
}
if (data.codecs) {
var videoCodecs = Object.keys(data.codecs.video).join(', ');
var audioCodecs = Object.keys(data.codecs.audio).join(', ');
if (videoCodecs) {
document.getElementById("details").innerHTML += "<br /><b>Supported video codecs:</b> " + videoCodecs + "<br />";
}
if (audioCodecs) {
document.getElementById("details").innerHTML += "<b>Supported audio codecs:</b> " + audioCodecs + "<br />";
}
// Check for optimal codecs
if (data.codecs.video.h264 || data.codecs.video.vp9) {
document.getElementById("details").innerHTML += "<small><i>(Good codec support for streaming)</i></small><br />";
} else if (!data.codecs.video.h264) {
document.getElementById("details").innerHTML += "<small><i>(H.264 codec support not detected, which may limit compatibility)</i></small><br />";
}
}
if (data.userRegion) {
document.getElementById("details").innerHTML += "<br /><b>User region:</b> " + data.userRegion + "<br />";
}
if (("hostMode" in data) && ("srflxMode" in data)){
if (!data.hostMode && !data.srflxMode){
document.getElementById("details").innerHTML += "<h3><span style='color:#fb4949'>WARNING: </span>Connection will be relayed via TURN server</h3>";
document.getElementById("details").innerHTML += "<u>No host or SRFLX ICE candidates collected. The browser or extension is likely blocking IP leaks</u><br />";
}
}
if (data.summary){
if (data.summary.download && data.summary.upload){
document.getElementById("details").innerHTML += "<br /><b>Max download bandwidth:</b> "+parseInt(data.summary.download/(1024*1024))+"-Mbps<br />";
document.getElementById("details").innerHTML += "<br /><b>Max Upload bandwidth:</b> "+parseInt(data.summary.upload/(1024*1024))+"-Mbps<br />";
}
}
//if (data.scores && data.scores.gaming && data.scores.gaming.classificationName && data.scores.rtc && data.scores.rtc.classificationName && data.scores.streaming && data.scores.streaming.classificationName){
// document.getElementById("details").innerHTML += "<br /><b>Gaming:</b> "+data.scores.gaming.classificationName+", <b>RTC:</b> "+data.scores.rtc.classificationName+", <b>Streaming:</b> //"+data.scores.streaming.classificationName+"<br />";
//}
if ("resolution" in data){
try {
if (parseInt(data.resolution.split("x ")[1])>parseInt(maxResolution.split("x ")[1])){
maxResolution = data.resolution;
}
} catch(e){}
//updateData("resolution", data.resolution);
}
if ("QLR" in data){
if (data.QLR == "none"){
QLR_1 +=1;
} else if (data.QLR.toLowerCase() == "cpu"){
QLR_2 +=1;
} else if (data.QLR.toLowerCase() == "network"){
QLR_3 +=1;
}
}
if ("info" in data){
if (data.info.Browser){
document.getElementById("details").innerHTML += "<br /><b>Browser used:</b> "+data.info.Browser+"<br />";
if (!data.info.Browser.startsWith("Chromium")){
document.getElementById("details").innerHTML += "<h3>A Chromium-based browser is recommended.</h3>";
}
}
if ("plugged_in" in data.info){
if (!data.info.plugged_in){
document.getElementById("details").innerHTML += "<h3>The user's power is not plugged in</h3>";
}
}
if (data.info.platform){
document.getElementById("details").innerHTML += "<br /><b>Platform (os):</b> "+data.info.platform+"<br />";
if (data.info.platform.toLowerCase() == "macintel"){
document.getElementById("details").innerHTML += "<i>(Intel or Apple Silicon)</i><br />";
}
}
if (data.info.gpGPU){
document.getElementById("details").innerHTML += "<br /><b>GPU:</b> "+data.info.gpGPU+"<br />";
}
if (data.info.CPU){
document.getElementById("details").innerHTML += "<br /><b>CPU:</b> "+data.info.CPU+"<br />";
}
if (data.info.conn_type){
document.getElementById("details").innerHTML += "<br /><b>Connection type:</b> "+data.info.conn_type+"<br />";
if (data.info.conn_type == "wifi"){
document.getElementById("details").innerHTML += "<h3>Avoid using WiFi if at all possible</h3>";
}
}
}
if (data.encoder){
if (data.encoder.toLowerCase() == "libvpx"){
document.getElementById("details").innerHTML += "<br /><b>Default video codec used:</b> VP8<br />";
} else {
document.getElementById("details").innerHTML += "<br /><b>Default video codec used:</b> "+data.encoder.toUpperCase()+"<br />";
}
}
});
// container
if (maxResolution){
document.getElementById("details").innerHTML += "<br /><b>Highest video resolution reached:</b> "+maxResolution+"<br />";
}
// Apply styling to the results messages
var total = QLR_1 + QLR_2 + QLR_3;
if (QLR_2/total > 0.5) {
document.getElementById("container").innerHTML += "<p class='error'>Serious CPU overload issues. Consider reducing the capture resolution.</p>";
} else if (QLR_2/total > 0.1) {
document.getElementById("container").innerHTML += "<p>Occasional CPU overload issues. Consider reducing the capture resolution.</p>";
}
if (QLR_3/total > 0.5) {
document.getElementById("container").innerHTML += "<p class='error'>The network quality or bandwidth limited the performance.</p>";
} else if (QLR_3/total > 0.1) {
document.getElementById("container").innerHTML += "<p>The network quality or bandwidth may have limited the performance.</p>";
}
document.getElementById("container").innerHTML += "<p><b>Average video bitrate:</b> " + parseInt(BBB/counter) + "-kbps</p>";
if (BBB/counter < 500) {
document.getElementById("container").innerHTML += "<small><i>Did they select an active camera?</i></small><h3>Bitrate is really <span class='error'>bad</span></h3>";
} else if (BBB/counter < 1000) {
document.getElementById("container").innerHTML += "<h3>Bitrate is poor</h3>";
} else if (BBB/counter < 2000) {
document.getElementById("container").innerHTML += "<h3>Bitrate a bit low</h3>";
} else {
document.getElementById("container").innerHTML += "<h3>Bitrate is <span class='success'>good</span></h3>";
}
document.getElementById("container").innerHTML += "<p><b>Average video buffer length:</b> " + parseInt(BUFF/BUFFCCC) + "-ms</p>";
if (BUFF/BUFFCCC > 500) {
document.getElementById("container").innerHTML += "<h3>Video delay is really <span class='error'>bad</span></h3>";
} else if (BUFF/BUFFCCC > 200) {
document.getElementById("container").innerHTML += "<h3>Video delay is poor</h3>";
} else if (BUFF/BUFFCCC > 100) {
document.getElementById("container").innerHTML += "<h3>Video delay is sub-optimal</h3>";
} else {
document.getElementById("container").innerHTML += "<h3>Video delay is <span class='success'>good</span></h3>";
}
document.getElementById("container").innerHTML += "<p><b>Average video packet loss:</b> " + (parseInt(PAK*1000/PAKCCC)/1000.0) + "%</p>";
if (PAK/PAKCCC > 3) {
document.getElementById("container").innerHTML += "<h3>Packet loss is extremely <span class='error'>bad</span>; Must Fix This</h3>";
} else if (PAK/PAKCCC > 0.8) {
document.getElementById("container").innerHTML += "<h3>Packet loss is quite <span class='error'>bad</span>; expect problems with audio and video</h3>";
} else if (PAK/PAKCCC > 0.15) {
document.getElementById("container").innerHTML += "<h3>Packet loss is a bit high; might be a testing-server issue though</h3>";
} else {
document.getElementById("container").innerHTML += "<h3>Packet loss is <span class='success'>good</span></h3>";
}
if (packetLossSpikes > 0) {
document.getElementById("details").innerHTML += "<div class='warning'><b>Packet loss spikes detected:</b> " + packetLossSpikes +
" occurrences of packet loss > 2%<br><small><i>(Spikes in packet loss can cause video freezes or audio dropouts even if average packet loss is low)</i></small></div>";
}
// Render the codec details button (original code)
if (arr.some(data => data.codecs || data.detectedCodecs)) {
document.getElementById("details").innerHTML += `<br />
<button id="view-codec-details" onclick="viewCodecDetails('${streamID}')">View Codec Support Details</button>
`;
window.viewCodecDetails = function(resultsId) {
window.open(`./codecs.html?results=${resultsId}`, '_blank');
};
}
}
var xmlhttp = new XMLHttpRequest();
var url = "https://record.vdo.workers.dev/?name="+streamID;
xmlhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var myArr = JSON.parse(this.responseText);
document.getElementById("container").innerHTML = "";
process(myArr);
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
function updateData(type, data) {
if (type == "target"){
bitrate.target = data;
}
if (type == "bitrate") {
bitrate.data = data;
plotData("bitrate", bitrate);
plotData("bitrate", bitrate);
plotData("bitrate", bitrate);
}
if (type == "buffer") {
buffer.data = data;
plotData("buffer", buffer);
plotData("buffer", buffer);
plotData("buffer", buffer);
}
if (type == "packetloss") {
packetloss.data = data;
plotData("packetloss", packetloss);
plotData("packetloss", packetloss);
plotData("packetloss", packetloss);
}
}
function plotData(type, stat) {
var canvas;
var context;
var yScale;
canvas = document.getElementById(stat.element);
context = canvas.getContext("2d");
if (isNaN(stat.data)) {
stat.data = 0;
}
var text = (canvas.previousElementSibling.innerHTML = stat.data);
var height = context.canvas.height;
var width = context.canvas.width;
var borderWidth = 5;
var offset = borderWidth * 2;
// Create gradient
var grd = context.createLinearGradient(0, 0, 0, height);
if (type == "bitrate") {
if (stat.target <= 3000){
grd.addColorStop(0, "#33C433");
grd.addColorStop(0.7, "#33C433");
grd.addColorStop(0.8, "#F3F304");
grd.addColorStop(0.92, "#F30404");
} else if (stat.target <= 4000){
grd.addColorStop(0, "#33C433");
grd.addColorStop(0.5, "#33C433");
grd.addColorStop(0.8, "#F3F304");
grd.addColorStop(0.92, "#F30404");
} else {
grd.addColorStop(0, "#33C433");
grd.addColorStop(0.3, "#33C433");
grd.addColorStop(0.8, "#F3F304");
grd.addColorStop(0.92, "#F30404");
}
} else {
// Higher values are red
grd.addColorStop(0, "#F30404");
grd.addColorStop(0.3, "#F3F304");
grd.addColorStop(0.7, "#33C433");
}
context.strokeStyle = "white";
context.fillStyle = grd;
//context.fillStyle = "#009933";
//context.imageSmoothingEnabled = true;
yScale = height / stat.max;
if (stat.data > stat.max) {
stat.data = stat.max;
}
if (type == "packetloss" && stat.data == 0.0) {
stat.data = 0.1;
}
var x = width - 1;
var y = height - stat.data * yScale;
var w = 1;
context.fillStyle = grd;
context.fillRect(x, y, w, height);
// shift everything to the left:
var imageData = context.getImageData(1, 0, width - 1, height);
context.putImageData(imageData, 0, 0);
// now clear the right-most pixels:
context.clearRect(width - 1, 0, 1, height);
}
</script>
</body>
</html>