mirror of
https://github.com/eliasstepanik/vdo.ninja.git
synced 2026-01-11 21:58:35 +00:00
508 lines
15 KiB
HTML
508 lines
15 KiB
HTML
<html>
|
|
<head>
|
|
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
|
|
<link rel="stylesheet" href="./speedtest.css?ver=1" />
|
|
<meta charset="utf8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>VDON Speed Test</title>
|
|
<style>
|
|
.fullscreen {
|
|
width:100%;
|
|
height: calc(100% - 35px);
|
|
position:absolute;
|
|
left:0;
|
|
display:block;
|
|
background-color: #444;
|
|
color:white;
|
|
margin: auto;
|
|
padding-top: 35px;
|
|
transition: all ease-in 1s;
|
|
animation-name: fadein;
|
|
animation-duration: .3s;
|
|
}
|
|
|
|
@keyframes fadein {
|
|
0% {opacity: 0.5;}
|
|
100% {opacity: 1;}
|
|
}
|
|
a {
|
|
color: white;
|
|
}
|
|
#controls button {
|
|
cursor: pointer;
|
|
display: inline;
|
|
padding: 20px;
|
|
}
|
|
.hidden {
|
|
display:none!important;
|
|
}
|
|
body {
|
|
text-align: center;
|
|
height:unset;
|
|
background: #444;
|
|
font-family: 'Noto Sans', sans-serif;
|
|
color:white;
|
|
}
|
|
h2 {
|
|
width: 760px;
|
|
max-width: 90%;
|
|
margin: auto;
|
|
}
|
|
|
|
li {
|
|
text-align: left;
|
|
}
|
|
|
|
button{
|
|
margin: 50px auto;
|
|
font-size: 120%;
|
|
padding: 20px 30px;
|
|
cursor:pointer;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="mainapp" >
|
|
<h1>
|
|
Video and stream quality check results
|
|
</h1>
|
|
<div id="container">
|
|
</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 getChromeVersion() {
|
|
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;
|
|
|
|
function process(arr) {
|
|
console.log(arr);
|
|
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 ("packetloss" in data){
|
|
updateData("packetloss", data.packetloss);
|
|
if (data.packetloss!==null){
|
|
PAK += parseFloat(data.packetloss) || 0;
|
|
PAKCCC += 1;
|
|
}
|
|
}
|
|
if (data.timestart){
|
|
document.getElementById("details").innerHTML += "<br /><b>Test start time:</b> "+timeConverter(data.timestart)+"<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){
|
|
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>";
|
|
}
|
|
|
|
}
|
|
}
|
|
});
|
|
// container
|
|
|
|
var total = QLR_1 + QLR_2 + QLR_3;
|
|
if (QLR_2/total>0.5){
|
|
document.getElementById("container").innerHTML += "Serious CPU overload issues. Consider reducing the capture resolution.<br />";
|
|
} else if (QLR_2/total>0.1){
|
|
document.getElementById("container").innerHTML += "Occassional CPU overload issues. Consider reducing the capture resolution.<br />";
|
|
}
|
|
if (QLR_3/total>0.5){
|
|
document.getElementById("container").innerHTML += "The network quality or bandwidth limited the performance.<br />";
|
|
} else if (QLR_3/total>0.1){
|
|
document.getElementById("container").innerHTML += "The network quality or bandwidth may have limited the performance.<br />";
|
|
}
|
|
|
|
document.getElementById("container").innerHTML += "The average video bitrate was: "+parseInt(BBB/counter)+"-kbps<br />";
|
|
|
|
if (BBB/counter<500){
|
|
document.getElementById("container").innerHTML += "<small><i>Did they select an active camera?</i></small><h3>Bitrate is really bad</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 good</h3>";
|
|
}
|
|
|
|
document.getElementById("container").innerHTML += "<br />The average video buffer length was: "+parseInt(BUFF/BUFFCCC)+"-ms<br />";
|
|
|
|
if (BUFF/BUFFCCC>500){
|
|
document.getElementById("container").innerHTML += "<h3>Video delay is really bad</h3><br />";
|
|
}
|
|
else if (BUFF/BUFFCCC>200){
|
|
document.getElementById("container").innerHTML += "<h3>Video delay is poor</h3><br />";
|
|
}
|
|
else if (BUFF/BUFFCCC>100){
|
|
document.getElementById("container").innerHTML += "<h3>Video delay is sub-optimal</h3><br />";
|
|
}
|
|
else {
|
|
document.getElementById("container").innerHTML += "<h3>Video delay is good</h3><br />";
|
|
}
|
|
|
|
document.getElementById("container").innerHTML += "The average video packet loss was: "+(parseInt(PAK*1000/PAKCCC)/1000.0)+"%<br />";
|
|
|
|
if (PAK/PAKCCC>3){
|
|
document.getElementById("container").innerHTML += "<h3>Packet loss is extremely bad; Must Fix This</h3>";
|
|
}
|
|
else if (PAK/PAKCCC>0.8){
|
|
document.getElementById("container").innerHTML += "<h3>Packet loss is quite bad; expect problems with audio and video</h3>";
|
|
}
|
|
else if (PAK/PAKCCC>.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 good</h3>";
|
|
}
|
|
|
|
console.log(QLR_1, QLR_2, QLR_3);
|
|
|
|
}
|
|
|
|
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);
|
|
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>
|