mirror of
https://github.com/eliasstepanik/vdo.ninja.git
synced 2026-01-23 03:18:29 +00:00
Merge pull request #579 from jcalado/14.0-alpha
speedtest graph color, button status, log copy
This commit is contained in:
commit
a0e545da4e
@ -32,9 +32,27 @@ h1 small {
|
|||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#feeds {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#feeds span {
|
||||||
|
flex: 1;
|
||||||
|
height: 30vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#feeds h3 {
|
||||||
|
color: whitesmoke;
|
||||||
|
margin: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
min-height: 30vh;
|
height: auto;
|
||||||
width: 39vw;
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#controls {
|
#controls {
|
||||||
@ -45,6 +63,10 @@ iframe {
|
|||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#controls button.active {
|
||||||
|
background-color: green;
|
||||||
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
@ -54,7 +76,8 @@ canvas {
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
background: #2a2a2a;
|
background: #2a2a2a;
|
||||||
padding: 20px 0px;
|
padding: 20px 0px;
|
||||||
border: 1px solid #383838;
|
border: 1px solid #383838;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#log ul {
|
#log ul {
|
||||||
@ -111,9 +134,19 @@ ol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
width: 90vw;
|
width: 100%;
|
||||||
min-height: 0;
|
}
|
||||||
|
|
||||||
|
#feeds {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#feeds h3 {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#statsdiv {display: none;}
|
#statsdiv {display: none;}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
244
speedtest.html
244
speedtest.html
@ -27,6 +27,24 @@
|
|||||||
})(window);
|
})(window);
|
||||||
var urlParams = new URLSearchParams(window.location.search);
|
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 loadIframe() {
|
function loadIframe() {
|
||||||
// this is pretty important if you want to avoid camera permission popup problems. YOu need to load the iFRAME after you load the parent body. A quick solution is like: <body onload=>loadIframe();"> !!!
|
// this is pretty important if you want to avoid camera permission popup problems. YOu need to load the iFRAME after you load the parent body. A quick solution is like: <body onload=>loadIframe();"> !!!
|
||||||
|
|
||||||
@ -66,7 +84,16 @@
|
|||||||
iframe.src = srcString;
|
iframe.src = srcString;
|
||||||
|
|
||||||
iframeContainer.appendChild(iframe);
|
iframeContainer.appendChild(iframe);
|
||||||
document.getElementById("container").appendChild(iframeContainer);
|
|
||||||
|
var title = document.createElement("h3");
|
||||||
|
title.innerText = "Local video feed";
|
||||||
|
iframeContainer.appendChild(title);
|
||||||
|
|
||||||
|
var feeds = document.createElement("div");
|
||||||
|
feeds.id = "feeds";
|
||||||
|
|
||||||
|
document.getElementById("container").appendChild(feeds);
|
||||||
|
document.getElementById("feeds").appendChild(iframeContainer);
|
||||||
|
|
||||||
var iframe = document.createElement("iframe");
|
var iframe = document.createElement("iframe");
|
||||||
var iframeContainer = document.createElement("span");
|
var iframeContainer = document.createElement("span");
|
||||||
@ -85,7 +112,12 @@
|
|||||||
iframe.src = srcString;
|
iframe.src = srcString;
|
||||||
|
|
||||||
iframeContainer.appendChild(iframe);
|
iframeContainer.appendChild(iframe);
|
||||||
document.getElementById("container").appendChild(iframeContainer);
|
|
||||||
|
var title = document.createElement("h3");
|
||||||
|
title.innerText = "Server video feed";
|
||||||
|
iframeContainer.appendChild(title);
|
||||||
|
|
||||||
|
document.getElementById("feeds").appendChild(iframeContainer);
|
||||||
|
|
||||||
var button = document.createElement("br");
|
var button = document.createElement("br");
|
||||||
document.getElementById("container").appendChild(button);
|
document.getElementById("container").appendChild(button);
|
||||||
@ -93,19 +125,12 @@
|
|||||||
var buttonContainer = document.createElement("div");
|
var buttonContainer = document.createElement("div");
|
||||||
buttonContainer.id = "controls";
|
buttonContainer.id = "controls";
|
||||||
|
|
||||||
var button = document.createElement("button");
|
|
||||||
button.innerHTML = "Disconnect";
|
|
||||||
button.className = "red";
|
|
||||||
button.onclick = function () {
|
|
||||||
iframe.contentWindow.postMessage({ close: true }, "*");
|
|
||||||
};
|
|
||||||
buttonContainer.appendChild(button);
|
|
||||||
|
|
||||||
var button = document.createElement("button");
|
var button = document.createElement("button");
|
||||||
button.innerHTML = "Low Bitrate";
|
button.innerHTML = "Low Bitrate";
|
||||||
button.className = "grey";
|
button.className = "grey";
|
||||||
button.onclick = function () {
|
button.onclick = function () {
|
||||||
iframe.contentWindow.postMessage({ bitrate: 30 }, "*");
|
iframe.contentWindow.postMessage({ bitrate: 30 }, "*");
|
||||||
|
bitrate.target = 30;
|
||||||
};
|
};
|
||||||
buttonContainer.appendChild(button);
|
buttonContainer.appendChild(button);
|
||||||
|
|
||||||
@ -114,14 +139,25 @@
|
|||||||
button.className = "grey";
|
button.className = "grey";
|
||||||
button.onclick = function () {
|
button.onclick = function () {
|
||||||
iframe.contentWindow.postMessage({ bitrate: 6000 }, "*");
|
iframe.contentWindow.postMessage({ bitrate: 6000 }, "*");
|
||||||
|
bitrate.target = 6000;
|
||||||
};
|
};
|
||||||
buttonContainer.appendChild(button);
|
buttonContainer.appendChild(button);
|
||||||
|
|
||||||
var button = document.createElement("button");
|
var button = document.createElement("button");
|
||||||
button.innerHTML = "Default Bitrate";
|
button.innerHTML = "Default Bitrate";
|
||||||
button.className = "grey";
|
button.className = "grey active";
|
||||||
button.onclick = function () {
|
button.onclick = function () {
|
||||||
iframe.contentWindow.postMessage({ bitrate: -1 }, "*");
|
iframe.contentWindow.postMessage({ bitrate: -1 }, "*");
|
||||||
|
bitrate.target = 3000;
|
||||||
|
};
|
||||||
|
buttonContainer.appendChild(button);
|
||||||
|
|
||||||
|
var button = document.createElement("button");
|
||||||
|
button.innerHTML = "Disconnect";
|
||||||
|
button.className = "red";
|
||||||
|
button.style.display = "none";
|
||||||
|
button.onclick = function () {
|
||||||
|
iframe.contentWindow.postMessage({ close: true }, "*");
|
||||||
};
|
};
|
||||||
buttonContainer.appendChild(button);
|
buttonContainer.appendChild(button);
|
||||||
|
|
||||||
@ -141,7 +177,34 @@
|
|||||||
|
|
||||||
eventer(messageEvent, function (e) {
|
eventer(messageEvent, function (e) {
|
||||||
if ("action" in e.data) {
|
if ("action" in e.data) {
|
||||||
logData("Action",e.data.action);
|
logData(e.data.action, e.data.value);
|
||||||
|
|
||||||
|
if (e.data.action == "new-view-connection") {
|
||||||
|
buttonContainer.querySelectorAll(
|
||||||
|
"#controls button:last-child"
|
||||||
|
)[0].style.display = "inline";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.data.action == "setVideoBitrate") {
|
||||||
|
buttonContainer.querySelectorAll("button").forEach((button) => {
|
||||||
|
button.classList.remove("active");
|
||||||
|
});
|
||||||
|
if (e.data.value == 30) {
|
||||||
|
document
|
||||||
|
.querySelectorAll("#controls button")[0]
|
||||||
|
.classList.add("active");
|
||||||
|
}
|
||||||
|
if (e.data.value == 6000) {
|
||||||
|
document
|
||||||
|
.querySelectorAll("#controls button")[1]
|
||||||
|
.classList.add("active");
|
||||||
|
}
|
||||||
|
if (e.data.value == -1) {
|
||||||
|
document
|
||||||
|
.querySelectorAll("#controls button")[2]
|
||||||
|
.classList.add("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ("stats" in e.data) {
|
if ("stats" in e.data) {
|
||||||
var out = "";
|
var out = "";
|
||||||
@ -149,41 +212,37 @@
|
|||||||
out += printValues(e.data.stats.inbound_stats[streamID]);
|
out += printValues(e.data.stats.inbound_stats[streamID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (out.split("Bitrate_in_kbps").length > 1) {
|
if (out.split("Bitrate_in_kbps").length > 1) {
|
||||||
|
|
||||||
for (var key in e.data.stats.inbound_stats[streamID]) {
|
for (var key in e.data.stats.inbound_stats[streamID]) {
|
||||||
|
|
||||||
if (key.startsWith("RTCMediaStreamTrack_receiver")) {
|
if (key.startsWith("RTCMediaStreamTrack_receiver")) {
|
||||||
var bitrate =
|
var bitrate =
|
||||||
e.data.stats.inbound_stats[streamID][key][
|
e.data.stats.inbound_stats[streamID][key][
|
||||||
"Bitrate_in_kbps"
|
"Bitrate_in_kbps"
|
||||||
];
|
];
|
||||||
plotData("bitrate-graph", bitrate, 6000);
|
updateData("bitrate", bitrate);
|
||||||
|
|
||||||
var buffer =
|
var buffer =
|
||||||
e.data.stats.inbound_stats[streamID][key][
|
e.data.stats.inbound_stats[streamID][key][
|
||||||
"Buffer_Delay_in_ms"
|
"Buffer_Delay_in_ms"
|
||||||
];
|
];
|
||||||
plotData("buffer-graph", buffer, 200);
|
updateData("buffer", buffer);
|
||||||
|
|
||||||
var packetloss =
|
var packetloss =
|
||||||
e.data.stats.inbound_stats[streamID][key][
|
e.data.stats.inbound_stats[streamID][key][
|
||||||
"packetLoss_in_percentage"
|
"packetLoss_in_percentage"
|
||||||
].toFixed(2);
|
];
|
||||||
plotData("packetloss-graph", packetloss, 3);
|
if (packetloss != undefined) {
|
||||||
|
packetloss = packetloss.toFixed(2);
|
||||||
|
updateData("packetloss", packetloss);
|
||||||
|
}
|
||||||
|
|
||||||
var resolution =
|
var resolution =
|
||||||
e.data.stats.inbound_stats[streamID][key][
|
e.data.stats.inbound_stats[streamID][key]["Resolution"];
|
||||||
"Resolution"
|
|
||||||
]
|
|
||||||
|
|
||||||
if(previousResolution != resolution) {
|
if (previousResolution != resolution) {
|
||||||
previousResolution = resolution;
|
previousResolution = resolution;
|
||||||
logData("Resolution", resolution);
|
logData("Resolution", resolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,10 +269,11 @@
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function logData(type, data){
|
function logData(type, data) {
|
||||||
var log = document.getElementById("log").getElementsByTagName("ul")[0];
|
var log = document.getElementById("log").getElementsByTagName("ul")[0];
|
||||||
var entry = document.createElement('li');
|
var entry = document.createElement('li');
|
||||||
entry.innerText = "[" + new Date().toLocaleTimeString() + "] " + type + " : " + data;
|
entry.textContent =
|
||||||
|
"[" + new Date().toLocaleTimeString() + "] " + type + " : " + data;
|
||||||
log.prepend(entry);
|
log.prepend(entry);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -252,17 +312,14 @@
|
|||||||
<div class="graph">
|
<div class="graph">
|
||||||
<h2>Packet Loss (%)</h2>
|
<h2>Packet Loss (%)</h2>
|
||||||
<span>0</span>
|
<span>0</span>
|
||||||
<canvas
|
<canvas id="packetloss-graph"></canvas>
|
||||||
id="packetloss-graph"
|
|
||||||
></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="log">
|
<div id="log" onclick="copyFunction(this.innerText)">
|
||||||
<h2>Log</h2>
|
<h2>Log <i class="las la-clipboard"></i></h2>
|
||||||
<ul></ul>
|
<ul></ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="explanation">
|
<div id="explanation">
|
||||||
<h2>How to use</h2>
|
<h2>How to use</h2>
|
||||||
<ol>
|
<ol>
|
||||||
@ -277,67 +334,118 @@
|
|||||||
CTRL + LeftClick on the new video to open stats that way)
|
CTRL + LeftClick on the new video to open stats that way)
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Bitrate, Buffer delay, and packet loss are
|
Bitrate, Buffer delay, and packet loss are important connection
|
||||||
important connection quality metrics
|
quality metrics
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Change the video bitrate by pressing the buttons below the video.
|
Change the video bitrate by pressing the buttons below the video. It
|
||||||
It should approach 6000-kbps if the network allows.
|
should approach 6000-kbps if the network allows.
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div id="statsdiv"></div>
|
<div id="statsdiv"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function plotData(element, data, max) {
|
var bitrate = {
|
||||||
|
element: "bitrate-graph",
|
||||||
|
data: 0,
|
||||||
|
max: 6000,
|
||||||
|
target: 3000,
|
||||||
|
};
|
||||||
|
var frames;
|
||||||
|
var buffer = {
|
||||||
|
element: "buffer-graph",
|
||||||
|
data: 0,
|
||||||
|
max: 200,
|
||||||
|
target: 100,
|
||||||
|
};
|
||||||
|
var packetloss = {
|
||||||
|
element: "packetloss-graph",
|
||||||
|
data: 0,
|
||||||
|
max: 3,
|
||||||
|
target: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateData(type, data) {
|
||||||
|
if (type == "bitrate") {
|
||||||
|
bitrate.data = data;
|
||||||
|
plotData("bitrate", bitrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "buffer") {
|
||||||
|
buffer.data = data;
|
||||||
|
plotData("buffer", buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "packetloss") {
|
||||||
|
packetloss.data = data;
|
||||||
|
plotData("packetloss", packetloss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function plotData(type, stat) {
|
||||||
var canvas;
|
var canvas;
|
||||||
var context;
|
var context;
|
||||||
var yScale;
|
var yScale;
|
||||||
|
|
||||||
canvas = document.getElementById(element);
|
canvas = document.getElementById(stat.element);
|
||||||
context = canvas.getContext("2d");
|
context = canvas.getContext("2d");
|
||||||
|
|
||||||
if (isNaN(data)) {
|
if (isNaN(stat.data)) {
|
||||||
data = 0;
|
stat.data = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = (canvas.previousElementSibling.innerHTML = data);
|
var text = (canvas.previousElementSibling.innerHTML = stat.data);
|
||||||
|
|
||||||
var height = context.canvas.height;
|
var height = context.canvas.height;
|
||||||
var width = context.canvas.width;
|
var width = context.canvas.width;
|
||||||
|
|
||||||
context.fillStyle = "#009933";
|
var borderWidth = 5;
|
||||||
context.imageSmoothingEnabled = true;
|
var offset = borderWidth * 2;
|
||||||
|
|
||||||
yScale = height / max;
|
// Create gradient
|
||||||
|
var grd = context.createLinearGradient(0, 0, 0, height);
|
||||||
|
|
||||||
if (data > max) {
|
if (type == "bitrate") {
|
||||||
data = max;
|
// Higher values are green
|
||||||
|
grd.addColorStop(0, "#33C433");
|
||||||
|
grd.addColorStop(0.7, "#F3F304");
|
||||||
|
grd.addColorStop(0.9, "#F30404");
|
||||||
|
} else {
|
||||||
|
// Higher values are red
|
||||||
|
grd.addColorStop(0, "#F30404");
|
||||||
|
grd.addColorStop(0.3, "#F3F304");
|
||||||
|
grd.addColorStop(0.7, "#33C433");
|
||||||
}
|
}
|
||||||
|
|
||||||
context.fillRect(
|
context.strokeStyle = "white";
|
||||||
width - 1,
|
context.fillStyle = grd;
|
||||||
height - data * yScale,
|
//context.fillStyle = "#009933";
|
||||||
1,
|
//context.imageSmoothingEnabled = true;
|
||||||
height
|
|
||||||
);
|
yScale = height / stat.target;
|
||||||
|
|
||||||
|
if (stat.data > stat.target) {
|
||||||
|
stat.data = stat.target;
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
// shift everything to the left:
|
||||||
var imageData = context.getImageData(
|
var imageData = context.getImageData(1, 0, width - 1, height);
|
||||||
1,
|
|
||||||
0,
|
|
||||||
width - 1,
|
|
||||||
height
|
|
||||||
);
|
|
||||||
context.putImageData(imageData, 0, 0);
|
context.putImageData(imageData, 0, 0);
|
||||||
// now clear the right-most pixels:
|
// now clear the right-most pixels:
|
||||||
context.clearRect(
|
context.clearRect(width - 1, 0, 1, height);
|
||||||
width - 1,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
height
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user