Files
archived-vdo.ninja/check.html
2025-05-09 03:01:56 -04:00

1540 lines
47 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 Stream 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;
margin: auto;
max-width: 90%;
}
li {
text-align: left;
}
button{
margin: 50px auto;
font-size: 120%;
padding: 20px 30px;
cursor:pointer;
}
</style>
</head>
<body>
<div class="fullscreen" id="page1">
<h1>
Welcome
</h1>
<br />
<h2>
This application will access your camera and complete a video test stream.
<br />
<br />
The full test will take a few minutes to complete.<br />
<button onclick="next1();">Continue</button>⭐⭐⭐⭐
</h2>
</div>
<div class="fullscreen hidden" id="page2">
<h1>
Please note, for best results:<br />
</h1>
<br />
<h2>
<li>Connect your computer to a wired connection, instead of Wi-Fi</li><br />
<li>Have no other applications open while running this test</li><br />
<li>If using a laptop, connect your laptop to a power outlet</li><br />
🌠<button onclick="next2();">Continue</button>⭐⭐⭐
</h2>
</div>
<div class="fullscreen hidden" id="page2a">
<h1>
This first step will measure your network bandwidth<br />
</h1>
<br />
<h2>
We will test against Cloudflare's speed test servers.<br /><br />
It will take a minute to complete once started.<br />
<br />
<div id="cloudflareresults">
🌠🌠<button id="cloudflare" disabled>Loading Files</button>⭐⭐
</div>
</h2>
</div>
<div class="fullscreen hidden" id="page3">
<h2>
The next step will access your camera and microphone.<br /><br />
<br />
Accept the camera and microphone permissions if prompted.<br /><br />
<small><i>No one will be able to see your video or audio, other than you.</i></small>
<br /><br />
<img src='./media/accept.png'/><br />
🌠🌠🌠<button onclick="next3();">Continue</button>
</h2>
</div>
<div id="mainapp" class="hidden">
<h1>
Video and stream quality check
</h1>
<div id="container">
</div>
<div class="hidden" 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 style="display:none;" id="explanation">
<div id="remote"></div>
<br />
Testing location: <select name="turnlist" id="turnlist" onchange="reloadTurn();" title="Select an exact location to test against">
<option selected value="">Automatic</option>
<option value="de1">Saarbruecken, Germany</option>
<option value="de2">Frankfurt, Germany</option>
<option value="fr1">Strasbourg, France</option>
<option value="bra1">São Paulo, Brazil</option>
<option value="pol1">Warsaw, Poland</option>
<option value="cae1">Montreal, Canada</option>
<option value="use1">Virgina, USA</option>
<option disabled value="usc1">Chicago, USA</option>
<option disabled value="usw1">Los Angeles, USA</option>
<option value="usw2">Oregon, USA</option>
<option value="aus1">Sydney, Australia</option>
<option value="jap1">Tokyo, Japan</option>
<option value="sing1">Singapore</option>
<option value="ind1">Mumbai, India</option>
<option value="pol1">Warsaw, Poland</option>
</select>
<br /><br /><br />
</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");
}
async function runCodecDetection() {
try {
console.log("Starting advanced codec detection...");
const detectedCodecs = await detectCodecs();
logged.push({detectedCodecs});
console.log("Codec detection complete", detectedCodecs);
return detectedCodecs;
} catch (error) {
console.error("Error in codec detection:", error);
logged.push({codecDetectionError: error.message});
return null;
}
}
// Call codec detection at appropriate time
function next2() {
document.getElementById("page2").classList.add("hidden");
document.getElementById("page2a").classList.remove("hidden");
// Run codec detection in parallel with speed test loading
runCodecDetection();
setTimeout(function() {
if (document.getElementById("playButton") && !document.getElementById("playButton").skip) {
next2a();
}
}, 10000);
}
function next2a(){
document.getElementById("page2a").classList.add("hidden");
document.getElementById("page3").classList.remove("hidden");
}
function next3(){
document.getElementById("page3").classList.add("hidden");
document.getElementById("mainapp").classList.remove("hidden");
loadIframe(region);
}
(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);
var quality_reason = "";
var encoder = "";
var Round_Trip_Time_ms = "";
var recordResults = false;
var iframe1 = document.createElement("iframe");
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 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 logged = [];
function logData(data) {
logged.push(data);
}
const stunServers = [
'stun:stun.l.google.com:19302',
'stun:stun1.l.google.com:19302',
'stun:stun.cloudflare.com:3478',
];
async function checkConnectionModes() {
let hostMode = false;
let srflxMode = false;
const iceConfig = { iceServers: [{ urls: stunServers }] };
const rtcPeerConnection = new RTCPeerConnection(iceConfig);
rtcPeerConnection.createDataChannel(''); // Create a dummy data channel
const offer = await rtcPeerConnection.createOffer();
rtcPeerConnection.onicecandidate = (event) => {
if (event.candidate) {
if (event.candidate.candidate.includes('srflx')) {
if (!srflxMode){
srflxMode= true;
if (hostMode){
logged.push({hostMode,srflxMode});
}
}
} else if (event.candidate.candidate.includes('host')) {
if (!hostMode){
hostMode = true;
if (srflxMode){
logged.push({hostMode,srflxMode});
}
}
}
} else {
rtcPeerConnection.onicecandidate = null;
if (!hostMode || !hostMode){
logged.push({hostMode,srflxMode});
}
}
};
rtcPeerConnection.setLocalDescription(offer);
}
try {
checkConnectionModes();
} catch(e){}
function reloadTurn(){
console.log("Reloading to change TURN servers");
loadIframe(document.getElementById("turnlist").value);
}
function updateTurnlist(value){
var select = document.getElementById("turnlist");
var selected = select.value;
select.innerHTML = "";
var opt = document.createElement("option");
opt.value = ""
opt.title = "Choose the closest location automatically";
opt.innerHTML = "Automatic";
select.appendChild(opt);
if (selected == ""){
opt.selected = true;
}
for (var i =0;i<value.length;i++){
var opt = document.createElement("option");
opt.value = value[i].locale;
opt.title = value[i].name;
opt.innerHTML = value[i].name;
select.appendChild(opt);
if (selected == opt.value){
opt.selected = true;
}
}
}
var eventMethod = window.addEventListener
? "addEventListener"
: "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message";
var previousResolution;
var timer= null;
var statsSent = false;
eventer(messageEvent, function (e) {
if ("action" in e.data) {
if (e.data.action == "available-speedtest-servers"){
console.warn("Speedtest server list loaded");
updateTurnlist(e.data.value);
}
if (e.data.action == "started-camera"){
loadIframe2();
document.getElementById("localVideoText").innerText = "Local video before transmission";
} else if (e.data.action == "screen-share-state" && e.data.value){
loadIframe2();
document.getElementById("localVideoText").innerText = "Local video before transmission";
}
if (e.data.action == "new-view-connection") {
if (timer===null){
timer = 0;
} else {
return;
}
buttonContainer.querySelectorAll(
"#controls button:last-child"
)[0].style.display = "inline";
logData({"timestart": Date.now()});
try {
logData({"peakhour": new Date(Date.now()).getHours()>7 && new Date(Date.now()).getHours()<11});
} catch(e){}
var showdetails = document.createElement("button");
showdetails.onclick = function(){
document.getElementById("graphs").classList.toggle('hidden');
}
showdetails.innerText = "Show testing details";
buttonContainer.appendChild(showdetails);
setTimeout(function(button){
button.click();
}, 90000,button);
}
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) {
var out = "";
for (var someValue in e.data.stats.inbound) {
out += printValues(e.data.stats.inbound[someValue]);
if (e.data.stats.inbound[someValue] && e.data.stats.inbound[someValue].info){
if (!statsSent){
statsSent = e.data.stats.inbound[someValue];
}
}
}
for (var someValue in e.data.stats.outbound) {
if (e.data.stats.outbound[someValue].quality_limitation_reason){
if (quality_reason != e.data.stats.outbound[someValue].quality_limitation_reason) {
quality_reason = e.data.stats.outbound[someValue].quality_limitation_reason;
logData({"QLR": quality_reason});
}
}
if (e.data.stats.outbound[someValue].encoder){
if (encoder != e.data.stats.outbound[someValue].encoder) {
encoder = e.data.stats.outbound[someValue].encoder;
logData({"encoder":encoder});
}
} else if (e.data.stats.outbound[someValue].video_codec){
if (encoder != e.data.stats.outbound[someValue].video_codec) {
encoder = e.data.stats.outbound[someValue].video_codec;
logData({"encoder":encoder});
}
}
}
for (var key in e.data.stats.inbound[streamID]){
if (typeof e.data.stats.inbound[streamID][key] == "object"){
if ("Bitrate_in_kbps" in e.data.stats.inbound[streamID][key]){
var bitrate = e.data.stats.inbound[streamID][key]["Bitrate_in_kbps"];
updateData("bitrate", bitrate);
}
if ("Jitter_Buffer_ms" in e.data.stats.inbound[streamID][key]){
var buffer = e.data.stats.inbound[streamID][key]["Jitter_Buffer_ms"];
updateData("buffer", buffer);
} else if ("Buffer_Delay_in_ms" in e.data.stats.inbound[streamID][key]){
var buffer = e.data.stats.inbound[streamID][key]["Buffer_Delay_in_ms"];
updateData("buffer", buffer);
} else if ("Added_Buffer_Delay_ms" in e.data.stats.inbound[streamID][key]){
console.log("Added_Buffer_Delay_ms");
var buffer = e.data.stats.inbound[streamID][key]["Added_Buffer_Delay_ms"];
updateData("buffer", buffer);
}
if ("packetLoss_in_percentage" in e.data.stats.inbound[streamID][key]){
var packetloss = e.data.stats.inbound[streamID][key]["packetLoss_in_percentage"];
if (packetloss != undefined) {
packetloss = packetloss.toFixed(2);
updateData("packetloss", packetloss);
}
}
if ("Resolution" in e.data.stats.inbound[streamID][key]){
var resolution = e.data.stats.inbound[streamID][key]["Resolution"];
if (previousResolution != resolution) {
previousResolution = resolution;
logData({"resolution": resolution});
}
}
}
}
}
});
var streamID = "";
var possible = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
for (var i = 0; i < 7; i++) {
streamID += possible.charAt(
Math.floor(Math.random() * possible.length)
);
}
if (urlParams.has("sid")) {
streamID = urlParams.get("sid");
} else if (urlParams.has("push")) {
streamID = urlParams.get("push");
}
var region = "";
if (urlParams.has("location")) {
region = urlParams.get("location");
} else if (urlParams.has("region")) {
region = urlParams.get("region");
}
var testType= "webcam&quality=0&css=speedtest.css";
if (urlParams.has("screen") || urlParams.has("ss") || urlParams.has("screenshare") || urlParams.has("screentest")) {
testType = "quality=0&screenshare&css=speedtest.css"
}
<!-- <option selected value="">Automatic</option> -->
<!-- <option value="de1">Saarbruecken, Germany</option> -->
<!-- <option value="de2">Frankfurt, Germany</option> -->
<!-- <option value="fr1">Strasbourg, France</option> -->
<!-- <option value="bra1">São Paulo, Brazil</option> -->
<!-- <option value="pol1">Warsaw, Poland</option> -->
<!-- <option value="cae1">Montreal, Canada</option> -->
<!-- <option value="use1">Virgina, USA</option> -->
<!-- <option disabled value="usc1">Chicago, USA</option> -->
<!-- <option disabled value="usw1">Los Angeles, USA</option> -->
<!-- <option value="usw2">Oregon, USA</option> -->
<!-- <option value="aus1">Sydney, Australia</option> -->
<!-- <option value="jap1">Tokyo, Japan</option> -->
<!-- <option value="sing1">Singapore</option> -->
<!-- <option value="ind1">Mumbai, India</option> -->
<!-- <option value="pol1">Warsaw, Poland</option> -->
function detectCodecs() {
const codecData = {
video: {},
audio: {},
webrtc: {},
webcodec: {},
mediaCapabilities: {}
};
// Basic mimeType detection function - returns all supported mime types
function getSupportedMimeTypes(media, types, codecs) {
const supported = [];
// First check simple types
types.forEach(type => {
const mimeType = `${media}/${type}`;
if (MediaRecorder.isTypeSupported(mimeType)) {
supported.push(mimeType);
}
});
// Then check with codecs
types.forEach(type => {
const mimeType = `${media}/${type}`;
codecs.forEach(codec => {
if (!codec) return;
const variation = `${mimeType};codecs=${codec.toLowerCase()}`;
if (MediaRecorder.isTypeSupported(variation)) {
supported.push(variation);
const codecKey = codec.toLowerCase();
if (media === 'video') {
codecData.video[codecKey] = codecData.video[codecKey] || {};
codecData.video[codecKey].mediaRecorder = true;
codecData.video[codecKey].canEncode = true;
} else {
codecData.audio[codecKey] = codecData.audio[codecKey] || {};
codecData.audio[codecKey].mediaRecorder = true;
codecData.audio[codecKey].canEncode = true;
}
}
});
// Check codec combinations for video/webm
if (media === 'video' && type === 'webm') {
codecs.forEach(videoCodec => {
if (!videoCodec) return;
const audioCodecs = ['opus', 'vorbis'];
audioCodecs.forEach(audioCodec => {
const variation = `${mimeType};codecs=${videoCodec.toLowerCase()},${audioCodec}`;
if (MediaRecorder.isTypeSupported(variation)) {
supported.push(variation);
}
});
});
}
});
return supported;
}
// WebRTC hardware acceleration checker
async function checkWebRTCHardwareAcceleration(codec) {
return new Promise(async (resolve) => {
try {
const config = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
iceCandidatePoolSize: 0
};
const pc1 = new RTCPeerConnection(config);
const pc2 = new RTCPeerConnection(config);
pc1.createDataChannel('test', {ordered: true});
pc1.onicecandidate = e => e.candidate && pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = e => e.candidate && pc1.addIceCandidate(e.candidate);
const canvas = document.createElement('canvas');
canvas.width = 1280;
canvas.height = 720;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#3498db';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const startTime = Date.now();
function animateCanvas() {
const elapsed = Date.now() - startTime;
ctx.fillStyle = '#3498db';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#e74c3c';
const x = 100 + Math.sin(elapsed / 500) * 100;
const y = canvas.height / 2 + Math.cos(elapsed / 500) * 100;
ctx.beginPath();
ctx.arc(x, y, 50, 0, Math.PI * 2);
ctx.fill();
if (Date.now() - startTime < 3000) {
requestAnimationFrame(animateCanvas);
}
}
animateCanvas();
const stream = canvas.captureStream(30);
stream.getVideoTracks().forEach(track => {
pc1.addTrack(track, stream);
});
const transceiver = pc1.getTransceivers()[0];
const codecs = RTCRtpSender.getCapabilities('video').codecs;
const targetCodec = codecs.find(c => {
return c.mimeType.toLowerCase().includes(codec.toLowerCase());
});
if (targetCodec) {
transceiver.setCodecPreferences([targetCodec]);
}
const offer = await pc1.createOffer();
await pc1.setLocalDescription(offer);
await pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
await pc1.setRemoteDescription(answer);
setTimeout(async () => {
let hardwareAccelerated = false;
let profile = null;
try {
const stats = await pc1.getStats();
stats.forEach(stat => {
if (stat.type === 'outbound-rtp' && stat.kind === 'video') {
if (stat.encoderImplementation) {
const implementation = stat.encoderImplementation.toLowerCase();
hardwareAccelerated = implementation.includes('hardware') ||
implementation === 'externalencoder' ||
implementation === 'mediafoundationvideoacceleration' ||
implementation.includes('accelerator');
}
if (stat.codecId) {
stats.forEach(s => {
if (s.id === stat.codecId && s.sdpFmtpLine) {
const match = s.sdpFmtpLine.match(/profile-level-id=([0-9a-f]+)/i);
if (match) {
profile = match[1];
}
}
});
}
}
});
} catch (e) {
console.error(`Error getting WebRTC stats: ${e.message}`);
}
pc1.close();
pc2.close();
stream.getTracks().forEach(track => track.stop());
resolve({
hardwareAccelerated,
profile,
webrtc: true,
canEncode: true,
canDecode: true,
webrtcHwEncoding: hardwareAccelerated
});
}, 2000);
} catch (error) {
console.error(`WebRTC check failed for ${codec}: ${error.message}`);
resolve({ webrtc: false });
}
});
}
// WebCodec encoder detection
async function checkWebCodecEncoder(codec, width = 1280, height = 720, framerate = 30) {
if (!('VideoEncoder' in window)) {
return { webcodec: false };
}
let result = { webcodec: false };
try {
const codecMapping = {
'vp8': 'vp8',
'vp9': 'vp09.00.10.08',
'av1': 'av01.0.04M.08',
'h264': 'avc1.42001E',
'h265': 'hev1.1.6.L93.B0'
};
const webcodecFormat = codecMapping[codec.toLowerCase()] || codec;
// Check hardware acceleration with prefer-hardware
const hwConfig = {
codec: webcodecFormat,
hardwareAcceleration: 'prefer-hardware',
width,
height,
bitrate: 2000000,
framerate
};
const hwSupport = await VideoEncoder.isConfigSupported(hwConfig);
if (hwSupport && hwSupport.supported) {
result = {
webcodec: true,
hardwareAccelerated: true,
config: hwSupport.config,
canEncode: true
};
} else {
// Check with prefer-software
const swConfig = {
codec: webcodecFormat,
hardwareAcceleration: 'prefer-software',
width,
height,
bitrate: 2000000,
framerate
};
const swSupport = await VideoEncoder.isConfigSupported(swConfig);
if (swSupport && swSupport.supported) {
result = {
webcodec: true,
hardwareAccelerated: false,
config: swSupport.config,
canEncode: true
};
}
}
} catch (error) {
console.error(`WebCodec encoder check failed for ${codec}: ${error.message}`);
}
return result;
}
// WebCodec decoder detection
async function checkWebCodecDecoder(codec, width = 1280, height = 720, framerate = 30) {
if (!('VideoDecoder' in window)) {
return { webcodec: false };
}
let result = { webcodec: false };
try {
const codecMapping = {
'vp8': 'vp8',
'vp9': 'vp09.00.10.08',
'av1': 'av01.0.04M.08',
'h264': 'avc1.42001E',
'h265': 'hev1.1.6.L93.B0'
};
const webcodecFormat = codecMapping[codec.toLowerCase()] || codec;
// Check hardware acceleration with prefer-hardware
const hwConfig = {
codec: webcodecFormat,
hardwareAcceleration: 'prefer-hardware',
codedWidth: width,
codedHeight: height,
description: new Uint8Array(0)
};
const hwSupport = await VideoDecoder.isConfigSupported(hwConfig);
if (hwSupport && hwSupport.supported) {
result = {
webcodec: true,
hardwareAccelerated: true,
config: hwSupport.config,
canDecode: true
};
} else {
// Check with prefer-software
const swConfig = {
codec: webcodecFormat,
hardwareAcceleration: 'prefer-software',
codedWidth: width,
codedHeight: height,
description: new Uint8Array(0)
};
const swSupport = await VideoDecoder.isConfigSupported(swConfig);
if (swSupport && swSupport.supported) {
result = {
webcodec: true,
hardwareAccelerated: false,
config: swSupport.config,
canDecode: true
};
}
}
} catch (error) {
console.error(`WebCodec decoder check failed for ${codec}: ${error.message}`);
}
return result;
}
// Main detection function
async function runDetection() {
// Define codec lists
const videoTypes = ["webm", "mp4", "x-matroska", "ogg"];
const audioTypes = ["webm", "mp4", "ogg", "x-matroska", "wav"];
const videoCodecs = [
"vp8", "vp9", "av1", "h264", "h265",
"vp9.0", "vp8.0", "avc1", "av1x", "h.264", "h.265",
"av01.0.04M.08", "vp09.00.10.08", "avc1.42001E"
];
const audioCodecs = [
"opus", "vorbis", "mp3", "aac", "pcm",
"mp4a.40.2", "mp4a", "L16", "wav", "flac"
];
// 1. Detect MediaRecorder support
const supportedVideoTypes = getSupportedMimeTypes("video", videoTypes, videoCodecs);
const supportedAudioTypes = getSupportedMimeTypes("audio", audioTypes, audioCodecs);
console.log(`Found ${supportedVideoTypes.length} supported video MediaRecorder formats`);
console.log(`Found ${supportedAudioTypes.length} supported audio MediaRecorder formats`);
// 2. Detect WebCodec support
if ('VideoEncoder' in window || 'VideoDecoder' in window) {
console.log('Checking WebCodec support...');
const resolutions = [
{ width: 640, height: 360 },
{ width: 1280, height: 720 },
{ width: 1920, height: 1080 }
];
const webcodecCodecs = ['vp8', 'vp9', 'h264', 'av1', 'h265'];
for (const codec of webcodecCodecs) {
for (const res of resolutions) {
// Check encoder
const encoderResult = await checkWebCodecEncoder(codec, res.width, res.height, 30);
if (encoderResult.webcodec) {
codecData.video[codec] = codecData.video[codec] || {};
codecData.video[codec].webcodec = true;
codecData.video[codec].canEncode = true;
// Store specific hardware acceleration for WebCodec encoder
codecData.video[codec].webcodecHwEncoding = encoderResult.hardwareAccelerated;
// If any resolution has hardware acceleration, we'll mark it as supported
if (encoderResult.hardwareAccelerated) {
codecData.video[codec].hardwareAccelerated = true;
// Store the min resolution that allows hardware acceleration
if (!codecData.video[codec].minHwResolution ||
res.width * res.height <
codecData.video[codec].minHwResolution.width * codecData.video[codec].minHwResolution.height) {
codecData.video[codec].minHwResolution = res;
codecData.video[codec].resolutionLimit = `${res.width}x${res.height}+`;
}
}
// Only set hardware as false if it hasn't been set to true already
else if (codecData.video[codec].hardwareAccelerated !== true) {
codecData.video[codec].hardwareAccelerated = false;
}
if (encoderResult.config) {
codecData.video[codec].webcodecConfig = codecData.video[codec].webcodecConfig || {};
codecData.video[codec].webcodecConfig.encoder = encoderResult.config;
}
}
// Check decoder
const decoderResult = await checkWebCodecDecoder(codec, res.width, res.height, 30);
if (decoderResult.webcodec) {
codecData.video[codec] = codecData.video[codec] || {};
codecData.video[codec].webcodec = true;
codecData.video[codec].canDecode = true;
// Store specific hardware acceleration for WebCodec decoder
codecData.video[codec].webcodecHwDecoding = decoderResult.hardwareAccelerated;
// If any resolution has hardware acceleration, we'll mark it as supported
if (decoderResult.hardwareAccelerated) {
codecData.video[codec].hardwareAccelerated = true;
// Store the min resolution that allows hardware acceleration
if (!codecData.video[codec].minHwResolution ||
res.width * res.height <
codecData.video[codec].minHwResolution.width * codecData.video[codec].minHwResolution.height) {
codecData.video[codec].minHwResolution = res;
codecData.video[codec].resolutionLimit = `${res.width}x${res.height}+`;
}
}
// Only set hardware as false if it hasn't been set to true already
else if (codecData.video[codec].hardwareAccelerated !== true) {
codecData.video[codec].hardwareAccelerated = false;
}
if (decoderResult.config) {
codecData.video[codec].webcodecConfig = codecData.video[codec].webcodecConfig || {};
codecData.video[codec].webcodecConfig.decoder = decoderResult.config;
}
}
}
}
} else {
console.log('WebCodec API not supported by this browser');
}
// 3. Detect WebRTC support and hardware acceleration
if ('RTCPeerConnection' in window) {
console.log('Checking WebRTC support and hardware acceleration...');
const webrtcCodecs = ['VP8', 'VP9', 'AV1', 'H264'];
for (const codec of webrtcCodecs) {
const result = await checkWebRTCHardwareAcceleration(codec);
if (result.webrtc) {
const normalizedCodec = codec.toLowerCase();
codecData.video[normalizedCodec] = codecData.video[normalizedCodec] || {};
codecData.video[normalizedCodec].webrtc = true;
codecData.video[normalizedCodec].canEncode = true;
codecData.video[normalizedCodec].canDecode = true;
// Only update hardware acceleration if not already set
if (result.hardwareAccelerated) {
codecData.video[normalizedCodec].hardwareAccelerated = true;
} else if (codecData.video[normalizedCodec].hardwareAccelerated !== true) {
codecData.video[normalizedCodec].hardwareAccelerated = false;
}
if (result.profile) {
codecData.video[normalizedCodec].profile = result.profile;
}
if (result.webrtcHwEncoding) {
codecData.video[normalizedCodec].webrtcHwEncoding = true;
}
}
}
} else {
console.log('WebRTC not supported by this browser');
}
// 4. Check scalability modes and MediaCapabilities
if ('mediaCapabilities' in navigator) {
console.log('Checking MediaCapabilities API support...');
const videoTypes = ['vp8', 'vp9', 'av1', 'h264', 'h265', 'avc1'];
const resolutions = [
{ width: 640, height: 360 },
{ width: 1280, height: 720 },
{ width: 1920, height: 1080 }
];
// Define scalability modes to test
const scalabilityModes = [
"L1T1", "L1T2", "L1T3",
"L2T1", "L2T2", "L2T3",
"L3T1", "L3T2", "L3T3",
"S2T1", "S2T2", "S2T3",
"S3T1", "S3T2", "S3T3"
];
// Check browser-specific capability type
const isFirefox = navigator.userAgent.indexOf("Firefox") >= 0;
const capabilityType = isFirefox ? "transmission" : "webrtc";
// Check WebRTC codec capabilities
const webrtcCodecs = [];
if ('RTCRtpSender' in window && RTCRtpSender.getCapabilities) {
try {
const rtcCodecs = RTCRtpSender.getCapabilities('video').codecs;
rtcCodecs.forEach(codec => {
if (!['video/red', 'video/ulpfec', 'video/rtx'].includes(codec.mimeType)) {
const codecName = codec.mimeType.replace("video/", "").toLowerCase();
webrtcCodecs.push({
name: codecName,
mimeType: codec.mimeType,
sdpFmtpLine: codec.sdpFmtpLine,
clockRate: codec.clockRate
});
}
});
console.log(`Found ${webrtcCodecs.length} supported WebRTC codecs`);
} catch (e) {
console.error(`Error getting RTCRtpSender.getCapabilities: ${e.message}`);
}
}
// Process each codec with MediaCapabilities
for (const codec of webrtcCodecs) {
try {
const capabilityPromises = [];
const codecScalabilityModes = [];
// Test different scalability modes
for (const mode of scalabilityModes) {
for (const res of resolutions) {
capabilityPromises.push(
navigator.mediaCapabilities.encodingInfo({
type: capabilityType,
video: {
contentType: codec.mimeType,
width: res.width,
height: res.height,
bitrate: 2000000,
framerate: 30,
scalabilityMode: mode
}
}).then(result => {
return { mode, res, result };
}).catch(e => {
return { mode, res, error: e };
})
);
}
}
const results = await Promise.all(capabilityPromises);
// Process results
let isSupported = false;
let isSmooth = false;
let isPowerEfficient = false;
let supportedModes = [];
results.forEach(({ mode, res, result, error }) => {
if (result && !error) {
if (result.supported) {
isSupported = true;
if (result.smooth) {
isSmooth = true;
}
if (result.powerEfficient) {
isPowerEfficient = true;
}
if (!supportedModes.includes(mode)) {
supportedModes.push(mode);
}
}
}
});
if (isSupported) {
const codecName = codec.name;
codecData.video[codecName] = codecData.video[codecName] || {};
codecData.video[codecName].mediaCapabilities = true;
codecData.video[codecName].scalabilityModes = supportedModes;
// Assume MediaCapabilities is testing decoding
codecData.video[codecName].canDecode = true;
// If powerEfficient is true, this is a good indicator of hardware acceleration
if (isPowerEfficient && codecData.video[codecName].hardwareAccelerated !== true) {
codecData.video[codecName].hardwareAccelerated = true;
codecData.video[codecName].details = (codecData.video[codecName].details || '') +
'MediaCapabilities reports this codec as power efficient. ';
}
// Store SDP format parameters if available
if (codec.sdpFmtpLine && !codecData.video[codecName].profile) {
const profileMatch = codec.sdpFmtpLine.match(/profile-level-id=([0-9a-f]+)/i);
if (profileMatch) {
codecData.video[codecName].profile = profileMatch[1];
}
}
}
} catch (error) {
console.error(`MediaCapabilities check failed for ${codec.name}: ${error.message}`);
}
}
} else {
console.log('MediaCapabilities API not supported by this browser');
}
// Get browser info
const browserInfo = {
userAgent: navigator.userAgent,
platform: navigator.platform,
vendor: navigator.vendor
};
// Return complete codec data
return {
video: codecData.video,
audio: codecData.audio,
browserInfo
};
}
// Run detection and return results
return runDetection();
}
function loadIframe(zone="") {
// 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();"> !!!
document.getElementById("container").innerHTML = "";
var iframeContainer = document.createElement("span");
iframe1.allow="autoplay;camera;microphone;display-capture;";
iframe1.allowtransparency="true";
iframe1.allowfullscreen ="true";
//iframe.allow = "autoplay";
var srcString = "./index.html?push=" + streamID + "&cleanoutput&privacy&"+testType+"&audiodevice=1&fullscreen&transparent&remote&maxbandwidth&speedtest="+zone;
if (urlParams.has("turn")) {
srcString = srcString + "&turn=" + urlParams.get("turn");
}
// we are changing some text on page load, just to demonstrate what's possible.
iframe1.onload = function (e) {
e.target.contentWindow.postMessage(
{
function: "changeHTML",
target: "add_camera",
value: "Select your Camera",
},
"*"
);
};
iframe1.src = srcString;
iframeContainer.appendChild(iframe1);
var title = document.createElement("h3");
if (urlParams.has("screen") || urlParams.has("ss") || urlParams.has("screenshare") || urlParams.has("screentest")) {
title.innerHTML = "Select the screen, tab, or window you intend to share.<br><br><small>For best results, the content being shared should contain motion. A blank static page likely will not be sufficient.</small>";
} else {
title.innerHTML = "Select the camera you intend to use.<br><br><small>For best results, the content being captured should contain motion. A blank static page will not be sufficient.</small>";
}
title.id = "localVideoText";
iframeContainer.appendChild(title);
var feeds = document.createElement("div");
feeds.id = "feeds";
document.getElementById("container").appendChild(feeds);
document.getElementById("feeds").appendChild(iframeContainer);
setInterval(function (iframe1) {
try {
iframe1.contentWindow.postMessage({ getStats: true }, "*");
} catch(e){
clearInterval(this);
}
}, 1000, iframe1);
}
var buttonContainer = document.createElement("div");
buttonContainer.id = "controls";
var button = document.createElement("button");
function loadIframe2(zone="") {
if (document.getElementById("testframe")){
return;
}
//document.getElementById("graphs").classList.remove('hidden');
var iframe = document.createElement("iframe");
var iframeContainer = document.createElement("span");
iframe.id = "testframe";
iframe.allow = "autoplay";
var srcString = "./index.html?view=" + streamID + "&cleanoutput&privacy&noaudio&transparent&bitrate=6000&scale=100&speedtest="+zone; // No TURN servers set on the reciever. Don't want to query for TURN servers needlessly.
if (urlParams.has("turn")) {
srcString = srcString + "&turn=" + urlParams.get("turn");
}
if (urlParams.has("buffer")) {
srcString = srcString + "&buffer=" + urlParams.get("buffer");
}
if (urlParams.has("id")) {
recordResults = urlParams.get("id") || false;
} else if (urlParams.has("session")) {
recordResults = urlParams.get("session") || false;
}
iframe.src = srcString;
iframeContainer.appendChild(iframe);
var title = document.createElement("h3");
title.innerText = "Video after traversing the Internet";
iframeContainer.appendChild(title);
document.getElementById("feeds").appendChild(iframeContainer);
var br = document.createElement("br");
document.getElementById("container").appendChild(br);
var div = document.createElement("h1");
buttonContainer.appendChild(div);
button.className = "red";
//button.disabled = true;
button.innerHTML = "Abort the test";
button.style.display = "none";
button.onclick = function (){
logData(statsSent);
if (!recordResults){
recordResults = "results_"+streamID;
document.getElementById("container").innerHTML = "<br />Link to results: <a target='_blank' href='https://vdo.ninja/results?id="+recordResults+"'>https://vdo.ninja/results?id="+recordResults+"</a><br /><br /><small><i>Results are anonymous and deleted after 7-days</i></small><br /><br /><br />Final Test Results:<br />";
document.getElementById("graphs").classList.remove('hidden');
}
var request = new XMLHttpRequest();
request.open('POST', "https://record.vdo.workers.dev/?name="+recordResults);
try {
request.send(JSON.stringify(logged));
} catch(e){
console.error(e);
}
timer = 91;
div.innerHTML = "Test ended";
clearInterval(interval);
try{
if (iframe){
iframe.contentWindow.postMessage({ close: true }, "*");
}
button.remove();
iframe1.remove();
iframe.remove();
feeds.style.display = "none";
} catch(e){};
};
buttonContainer.appendChild(button);
document.getElementById("container").appendChild(buttonContainer);
var interval = setInterval(function (iframe1) {
if (timer==90){
document.body.innerHTML = "<h1>Test complete. Thank you</h1>";
return;
}
if (timer>90){
clearInterval(interval);
return;
}
if (timer == 0){
iframe.contentWindow.postMessage({ bitrate: 2500 }, "*");
bitrate.target = 2500;
updateData("target", bitrate.target);
}
if (timer == 30){
iframe.contentWindow.postMessage({ bitrate: 4000 }, "*");
bitrate.target = 4000;
updateData("target", bitrate.target);
}
if (timer == 60){
iframe.contentWindow.postMessage({ bitrate: 6000 }, "*");
bitrate.target = 6000;
updateData("target", bitrate.target);
}
if (timer!==null){
timer+=1
div.innerHTML = "Test completes in "+(90-timer)+" seconds";
}
try {
if (iframe1){
iframe1.contentWindow.postMessage({ getStats: true }, "*");
}
} catch(e){
clearInterval(interval);
}
}, 1000, iframe);
}
var bitrate = {
element: "bitrate-graph",
data: 0,
max: 6500,
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,
};
function updateData(type, 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);
}
logData({[type]: data});
}
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 == 2500){
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 if (stat.target == 6000){
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>
<script type="module">
import SpeedTest from 'https://cdn.skypack.dev/@cloudflare/speedtest';
const controlEl = document.getElementById('controls');
const playButton = document.getElementById("cloudflare");
const resultsCl = document.getElementById("cloudflareresults");
var Runstate = false;
const engine = new SpeedTest({
autoStart: false
});
engine.onRunningChange = (running) => {playButton.textContent = running ? 'Running...' : 'Finished!';Runstate=running;}
engine.onResultsChange = ({ type }) => {
console.log(type);
if (Runstate){
console.log(engine.results.raw);
}
resultsCl.style.color = "green";
resultsCl.innerHTML = Runstate ? '<b>Please wait</b><br .><br .>Testing in progress: '+type : 'Finished!';
};
engine.onFinish = results => {
console.log(results.getSummary());
console.log(results.getScores());
logData({['summary']: results.getSummary()});
logData({['scores']: results.getScores()});
next2a();
};
playButton.skip=true;
playButton.textContent = "Start test";
playButton.disabled = null;
playButton.onclick = function(){playButton.disabled=true;playButton.onclick=null;engine.play();}
</script>
</body>
</html>