mirror of
https://github.com/SrIzan10/vdo.ninja.git
synced 2026-05-01 11:05:24 +00:00
Add files via upload
This commit is contained in:
638
check.html
638
check.html
@@ -186,9 +186,28 @@
|
||||
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();
|
||||
@@ -545,6 +564,622 @@
|
||||
<!-- <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="") {
|
||||
@@ -673,11 +1308,10 @@
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('POST', "https://record.vdo.workers.dev/?name="+recordResults);
|
||||
try {
|
||||
logged = JSON.stringify(logged);
|
||||
request.send(JSON.stringify(logged));
|
||||
} catch(e){
|
||||
console.error(e);
|
||||
}
|
||||
request.send(logged);
|
||||
|
||||
timer = 91;
|
||||
div.innerHTML = "Test ended";
|
||||
|
||||
1769
codecs.html
Normal file
1769
codecs.html
Normal file
File diff suppressed because it is too large
Load Diff
151
electron.html
151
electron.html
@@ -278,7 +278,36 @@
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
#lastUrls {
|
||||
/* Keep your original styles for the select element */
|
||||
font-size: calc(16px + 0.3vw);
|
||||
width: 730px;
|
||||
height: 100%;
|
||||
flex: 20;
|
||||
border-radius: 10px;
|
||||
padding: 1em;
|
||||
background: #101520;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* These styles target the dropdown list in Webkit browsers */
|
||||
#lastUrls::-webkit-listbox {
|
||||
max-height: 200px !important;
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
#lastUrls {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #384861 #182031;
|
||||
}
|
||||
|
||||
/* This will help force the height of the dropdown menu in many browsers */
|
||||
@supports (-moz-appearance:none) {
|
||||
#lastUrls {
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -321,28 +350,96 @@
|
||||
* tree. Alternative licencing options can be made available on request.
|
||||
*
|
||||
*/
|
||||
var lastUrls = JSON.parse(localStorage.getItem('lastUrls'));
|
||||
if (lastUrls != undefined) {
|
||||
document.querySelector("#changeText").value = lastUrls[0];
|
||||
if (lastUrls.length>0){
|
||||
lastUrls.forEach((url)=>{
|
||||
var o = document.createElement('option');
|
||||
o.value = url;
|
||||
o.text = url;
|
||||
document.querySelector("#lastUrls").appendChild(o);
|
||||
})
|
||||
} else {
|
||||
document.querySelector("#history").style.display="none";
|
||||
}
|
||||
} else {
|
||||
document.querySelector("#history").style.display="none";
|
||||
}
|
||||
|
||||
|
||||
function setUrl(){
|
||||
document.querySelector("#changeText").value = document.querySelector("#lastUrls").value;
|
||||
gohere();
|
||||
}
|
||||
|
||||
function createCustomScrollableDropdown() {
|
||||
const originalSelect = document.querySelector('#lastUrls');
|
||||
if (!originalSelect) return;
|
||||
|
||||
// Create custom dropdown container
|
||||
const dropdownContainer = document.createElement('div');
|
||||
dropdownContainer.id = 'custom-lastUrls-container';
|
||||
dropdownContainer.style.position = 'relative';
|
||||
dropdownContainer.style.width = '730px';
|
||||
dropdownContainer.style.flex = '20';
|
||||
|
||||
// Create the dropdown header (what shows when closed)
|
||||
const dropdownHeader = document.createElement('div');
|
||||
dropdownHeader.id = 'custom-lastUrls-header';
|
||||
dropdownHeader.style.fontSize = 'calc(16px + 0.3vw)';
|
||||
dropdownHeader.style.padding = '1em';
|
||||
dropdownHeader.style.borderRadius = '10px';
|
||||
dropdownHeader.style.background = '#FFF';
|
||||
dropdownHeader.style.color = '#000';
|
||||
dropdownHeader.style.cursor = 'pointer';
|
||||
dropdownHeader.textContent = originalSelect.options.length > 0 ?
|
||||
originalSelect.options[0].text : 'History';
|
||||
|
||||
// Create the dropdown list (what shows when opened)
|
||||
const dropdownList = document.createElement('div');
|
||||
dropdownList.id = 'custom-lastUrls-list';
|
||||
dropdownList.style.display = 'none';
|
||||
dropdownList.style.position = 'absolute';
|
||||
dropdownList.style.width = '100%';
|
||||
dropdownList.style.maxHeight = '200px';
|
||||
dropdownList.style.overflowY = 'auto';
|
||||
dropdownList.style.background = '#DDD';
|
||||
dropdownList.style.borderRadius = '10px';
|
||||
dropdownList.style.zIndex = '100';
|
||||
|
||||
// Add options to the dropdown list
|
||||
Array.from(originalSelect.options).forEach((option, index) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'custom-lastUrls-item';
|
||||
item.style.padding = '0.5em 1em';
|
||||
item.style.cursor = 'pointer';
|
||||
item.textContent = option.text;
|
||||
item.dataset.value = option.value;
|
||||
|
||||
if (index % 2 === 1) {
|
||||
item.style.backgroundColor = '#FFF';
|
||||
}
|
||||
|
||||
item.addEventListener('click', function() {
|
||||
dropdownHeader.textContent = this.textContent;
|
||||
dropdownList.style.display = 'none';
|
||||
|
||||
// Update the original select value and trigger its change event
|
||||
originalSelect.value = this.dataset.value;
|
||||
const event = new Event('change');
|
||||
originalSelect.dispatchEvent(event);
|
||||
});
|
||||
|
||||
dropdownList.appendChild(item);
|
||||
});
|
||||
|
||||
// Toggle dropdown on header click
|
||||
dropdownHeader.addEventListener('click', function() {
|
||||
const isDisplayed = dropdownList.style.display === 'block';
|
||||
dropdownList.style.display = isDisplayed ? 'none' : 'block';
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!dropdownContainer.contains(e.target)) {
|
||||
dropdownList.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Add everything to the container
|
||||
dropdownContainer.appendChild(dropdownHeader);
|
||||
dropdownContainer.appendChild(dropdownList);
|
||||
|
||||
// Replace the original select with our custom dropdown
|
||||
originalSelect.style.display = 'none';
|
||||
originalSelect.parentNode.insertBefore(dropdownContainer, originalSelect);
|
||||
}
|
||||
|
||||
function resetHistory(){
|
||||
localStorage.clear();
|
||||
document.querySelector('#lastUrls').innerHTML = '';
|
||||
@@ -526,7 +623,6 @@ function createSpecialDeviceLink(deviceLabel) {
|
||||
link.style.transition = 'opacity 2s ease-in-out';
|
||||
link.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
|
||||
link.style.fontFamily = 'Arial, sans-serif';
|
||||
link.style.zIndex = '1000';
|
||||
link.title = "Make this video device fully fill the window, making it perfect for screen capture.";
|
||||
|
||||
document.body.appendChild(link);
|
||||
@@ -656,7 +752,7 @@ function addUrlToHistory(url){
|
||||
}
|
||||
if ( lastUrls[0] != url ) {
|
||||
lastUrls.unshift(url);
|
||||
if (lastUrls.length == 6) {
|
||||
if (lastUrls.length == 100) {
|
||||
lastUrls.pop();
|
||||
}
|
||||
}
|
||||
@@ -696,6 +792,25 @@ function gohere(){
|
||||
}
|
||||
window.location = url;
|
||||
};
|
||||
|
||||
var lastUrls = JSON.parse(localStorage.getItem('lastUrls'));
|
||||
if (lastUrls != undefined) {
|
||||
document.querySelector("#changeText").value = lastUrls[0];
|
||||
if (lastUrls.length > 0) {
|
||||
lastUrls.forEach((url) => {
|
||||
var o = document.createElement('option');
|
||||
o.value = url;
|
||||
o.text = url;
|
||||
document.querySelector("#lastUrls").appendChild(o);
|
||||
});
|
||||
// Create custom dropdown after populating
|
||||
createCustomScrollableDropdown();
|
||||
} else {
|
||||
document.querySelector("#history").style.display = "none";
|
||||
}
|
||||
} else {
|
||||
document.querySelector("#history").style.display = "none";
|
||||
}
|
||||
getPermssions();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
157
ip.html
Normal file
157
ip.html
Normal file
@@ -0,0 +1,157 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>IP Address Decoder</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-top: 0;
|
||||
}
|
||||
.input-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
}
|
||||
textarea {
|
||||
height: 80px;
|
||||
resize: vertical;
|
||||
}
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.result {
|
||||
margin-top: 20px;
|
||||
}
|
||||
footer {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
color: #777;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>IP Address Decoder</h1>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="obfuscatedInput">Obfuscated Username:</label>
|
||||
<input type="text" id="obfuscatedInput" placeholder="Enter encoded string (with or without timestamp part)">
|
||||
</div>
|
||||
|
||||
<button id="decodeButton">Decode IP Address</button>
|
||||
|
||||
<div class="result">
|
||||
<label for="decodedOutput">Decoded IP Address:</label>
|
||||
<textarea id="decodedOutput" readonly></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>IP Address Decoder Tool</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function decodeIpAddress(obfuscatedUsername) {
|
||||
try {
|
||||
// Get the obfuscated part (remove timestamp part if it exists)
|
||||
let encoded = obfuscatedUsername;
|
||||
if (obfuscatedUsername.includes(':')) {
|
||||
encoded = obfuscatedUsername.split(':')[1];
|
||||
}
|
||||
|
||||
// 1. Move last character to the beginning
|
||||
if (encoded.length > 1) {
|
||||
const lastChar = encoded.charAt(encoded.length - 1);
|
||||
encoded = lastChar + encoded.substring(0, encoded.length - 1);
|
||||
}
|
||||
|
||||
// 2. Restore base64 standard format
|
||||
encoded = encoded
|
||||
.replace(/-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
// Add padding if needed
|
||||
while (encoded.length % 4) {
|
||||
encoded += '=';
|
||||
}
|
||||
|
||||
// 3. Decode base64
|
||||
const decoded = atob(encoded);
|
||||
|
||||
// 4. Remove the salt
|
||||
const salt = "x";
|
||||
if (decoded.endsWith(salt)) {
|
||||
return decoded.substring(0, decoded.length - salt.length);
|
||||
}
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
console.error('Error decoding IP:', e);
|
||||
return 'Error: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('decodeButton').addEventListener('click', function() {
|
||||
const obfuscatedUsername = document.getElementById('obfuscatedInput').value.trim();
|
||||
if (!obfuscatedUsername) {
|
||||
document.getElementById('decodedOutput').value = 'Please enter an obfuscated username';
|
||||
return;
|
||||
}
|
||||
|
||||
const decodedIp = decodeIpAddress(obfuscatedUsername);
|
||||
document.getElementById('decodedOutput').value = decodedIp || 'Failed to decode';
|
||||
});
|
||||
|
||||
// Add sample data functionality
|
||||
function addSampleData() {
|
||||
const sampleButton = document.createElement('button');
|
||||
sampleButton.textContent = 'Try Sample Data';
|
||||
sampleButton.style.marginLeft = '10px';
|
||||
sampleButton.style.backgroundColor = '#2196F3';
|
||||
|
||||
document.getElementById('decodeButton').after(sampleButton);
|
||||
|
||||
sampleButton.addEventListener('click', function() {
|
||||
document.getElementById('obfuscatedInput').value = 'l0VwNVdlNjE0LjEyOS44OS4yMTF4';
|
||||
document.getElementById('decodeButton').click();
|
||||
});
|
||||
}
|
||||
|
||||
addSampleData();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
329
results.html
329
results.html
@@ -1,63 +1,202 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
|
||||
<link rel="stylesheet" href="./speedtest.css?ver=1" />
|
||||
<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>
|
||||
.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;
|
||||
: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;
|
||||
}
|
||||
|
||||
@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;
|
||||
background: var(--primary-bg);
|
||||
font-family: 'Noto Sans', sans-serif;
|
||||
color:white;
|
||||
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;
|
||||
}
|
||||
|
||||
li {
|
||||
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 {
|
||||
margin: 50px auto;
|
||||
font-size: 120%;
|
||||
padding: 20px 30px;
|
||||
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>
|
||||
@@ -233,9 +372,15 @@
|
||||
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);
|
||||
@@ -254,13 +399,18 @@
|
||||
BUFFCCC += 1;
|
||||
}
|
||||
}
|
||||
if ("packetloss" in data){
|
||||
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 />";
|
||||
}
|
||||
@@ -271,6 +421,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
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){
|
||||
|
||||
@@ -355,65 +528,71 @@
|
||||
}
|
||||
|
||||
|
||||
// Apply styling to the results messages
|
||||
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 />";
|
||||
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 += "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 += "<p>Occasional CPU overload issues. Consider reducing the capture resolution.</p>";
|
||||
}
|
||||
|
||||
document.getElementById("container").innerHTML += "The average video bitrate was: "+parseInt(BBB/counter)+"-kbps<br />";
|
||||
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 style='color:#fb4949'>bad<span></h3>";
|
||||
}
|
||||
else if (BBB/counter<1000){
|
||||
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){
|
||||
} else if (BBB/counter < 2000) {
|
||||
document.getElementById("container").innerHTML += "<h3>Bitrate a bit low</h3>";
|
||||
}
|
||||
else {
|
||||
document.getElementById("container").innerHTML += "<h3>Bitrate is <span style='color:#60ff60'>good<span></h3>";
|
||||
} else {
|
||||
document.getElementById("container").innerHTML += "<h3>Bitrate is <span class='success'>good</span></h3>";
|
||||
}
|
||||
|
||||
document.getElementById("container").innerHTML += "<br />The average video buffer length was: "+parseInt(BUFF/BUFFCCC)+"-ms<br />";
|
||||
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 style='color:#fb4949'>bad<span></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 <span style='color:#60ff60'>good<span></h3><br />";
|
||||
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 += "The average video packet loss was: "+(parseInt(PAK*1000/PAKCCC)/1000.0)+"%<br />";
|
||||
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 style='color:#fb4949'>bad<span>; Must Fix This</h3>";
|
||||
}
|
||||
else if (PAK/PAKCCC>0.8){
|
||||
document.getElementById("container").innerHTML += "<h3>Packet loss is quite <span style='color:#fb4949'>bad<span>; expect problems with audio and video</h3>";
|
||||
}
|
||||
else if (PAK/PAKCCC>.15){
|
||||
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 style='color:#60ff60'>good<span></h3>";
|
||||
} else {
|
||||
document.getElementById("container").innerHTML += "<h3>Packet loss is <span class='success'>good</span></h3>";
|
||||
}
|
||||
|
||||
console.log(QLR_1, QLR_2, QLR_3);
|
||||
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();
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<!-- speedtest.html !-->
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="./lineawesome/css/line-awesome.min.css" />
|
||||
<link rel="stylesheet" href="./speedtest.css?ver=7" />
|
||||
<meta charset="utf8" />
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
|
||||
<link id="favicon1" rel="icon" type="image/png" sizes="32x32" href="./media/favicon-32x32.png" />
|
||||
<link id="favicon2" rel="icon" type="image/png" sizes="16x16" href="./media/favicon-16x16.png" />
|
||||
<link id="favicon3" rel="icon" href="./media/favicon.ico" />
|
||||
<link rel="alternate" type="text/markdown" href="/rawdoc.md" title="Developer and User Documentation">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>VDON Speed Test</title>
|
||||
</head>
|
||||
@@ -230,7 +236,7 @@
|
||||
iframe.allowfullscreen ="true";
|
||||
|
||||
//iframe.allow = "autoplay";
|
||||
var srcString = "./?push=" + streamID + "&cleanoutput&privacy&"+testType+"&audiodevice=0&fullscreen&transparent&remote&speedtest="+zone;
|
||||
var srcString = "./?push=" + streamID + "&cleanish&privacy&"+testType+"&audiodevice=0&fullscreen&transparent&remote&speedtest="+zone;
|
||||
|
||||
if (urlParams.has("turn")) {
|
||||
srcString = srcString + "&turn=" + urlParams.get("turn");
|
||||
@@ -274,7 +280,7 @@
|
||||
|
||||
iframe.allow = "autoplay";
|
||||
// I've removed &privacy from the view link, and left it just on the push link. This hopefully solves compatibility issues
|
||||
var srcString = "./?view=" + streamID + "&cleanoutput&noaudio&scale=0&speedtest="+zone; // No TURN servers set on the reciever. Don't want to query for TURN servers needlessly.
|
||||
var srcString = "./?view=" + streamID + "&cleanish&noaudio&scale=0&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");
|
||||
|
||||
@@ -293,7 +293,14 @@
|
||||
<option value="url" selected>Website URL</option>
|
||||
<option value="twitch">Twitch</option>
|
||||
</select >
|
||||
|
||||
<select style="border-radius:10px;margin-left:5px;margin-top: 13px;width:unset!important;" onchange="rotatePage();" class="changeText" id="zoomLevel" title="Change the scale of the content">
|
||||
<option value="1" selected>100% (Normal)</option>
|
||||
<option value="0.75">75%</option>
|
||||
<option value="0.5">50%</option>
|
||||
<option value="1.25">125%</option>
|
||||
<option value="1.5">150%</option>
|
||||
<option value="2">200%</option>
|
||||
</select>
|
||||
<select style="border-radius:10px;margin-left:5px;margin-top: 13px;width:unset!important;" onchange="rotatePage();" class="changeText" id="rotation" title="Which video bitrate target would you prefer?" >
|
||||
<option value="0" selected>No Rotation</option>
|
||||
<option value="90">↩️90° CW</option>
|
||||
@@ -406,6 +413,11 @@ if (urlParams.has("rotate")){
|
||||
}
|
||||
|
||||
|
||||
if (urlParams.has("zoom")){
|
||||
document.querySelector("#zoomLevel").value = urlParams.get("zoom") || 1;
|
||||
} else if (localStorage.getItem('zoomLevel')){
|
||||
document.querySelector("#zoomLevel").value = localStorage.getItem('zoomLevel') || 1;
|
||||
}
|
||||
|
||||
if (urlParams.has("flip")){
|
||||
document.querySelector("#transform").value = urlParams.get("flip") || 0;
|
||||
@@ -677,97 +689,99 @@ async function loadPage(){
|
||||
document.body.appendChild(iframe);
|
||||
}
|
||||
|
||||
|
||||
function rotatePage(){
|
||||
|
||||
var rotate = document.querySelector("#rotation").value || 0;
|
||||
var flip = document.querySelector("#transform").value;
|
||||
var zoomLevel = parseFloat(document.querySelector("#zoomLevel").value || 1);
|
||||
|
||||
updateURL("rotate="+rotate, true);
|
||||
updateURL("flip="+flip, true);
|
||||
updateURL("zoom="+zoomLevel, true);
|
||||
|
||||
localStorage.setItem('iframeURL', document.getElementById('iframeURL').value);
|
||||
localStorage.setItem('rotation', document.getElementById('rotation').value);
|
||||
//localStorage.setItem('backgroundColor', document.getElementById('backgroundColor').value);
|
||||
localStorage.setItem('transform', document.getElementById('transform').value);
|
||||
localStorage.setItem('zoomLevel', document.getElementById('zoomLevel').value);
|
||||
|
||||
if (rotate==180){
|
||||
iframe.style.transform = "rotate(180deg)";
|
||||
iframe.style.width = "100vw";
|
||||
iframe.style.height = "calc(100vh - "+menuOffset+")";
|
||||
iframe.style.transformOrigin = "0 0;";
|
||||
iframe.style.position = "rotate(180deg)";
|
||||
iframe.style.left = "100vw";
|
||||
iframe.style.top = "calc(100vh)";
|
||||
|
||||
if (flip==1){
|
||||
iframe.style.transform += " scaleX(-1)";
|
||||
iframe.style.top = "calc(" + (iframe.style.top) +" + )";
|
||||
iframe.style.left = "calc(" + (iframe.style.left) +" - 100vw)";
|
||||
} else if (flip==2){
|
||||
iframe.style.transform += " scaleY(-1)";
|
||||
iframe.style.top = "calc(" + (iframe.style.top) +" + "+menuOffset+" - 100vh)";
|
||||
iframe.style.left = "calc(" + (iframe.style.left) +" )";
|
||||
// Remove existing iframe if it exists
|
||||
if (iframe.parentNode) {
|
||||
iframe.parentNode.removeChild(iframe);
|
||||
}
|
||||
|
||||
} else if (rotate==270){
|
||||
iframe.style.transform = "rotate(270deg)";
|
||||
iframe.style.left = "0";
|
||||
iframe.style.top = "100vh";
|
||||
iframe.style.transformOrigin = "0 0;";
|
||||
iframe.style.width = "calc(100vh - "+menuOffset+")";
|
||||
iframe.style.height = "100vw";
|
||||
|
||||
if (flip==1){
|
||||
iframe.style.transform += " scaleX(-1)";
|
||||
iframe.style.top = "calc(" + (iframe.style.top) +" + "+menuOffset+" - 100vh)";
|
||||
iframe.style.left = "calc(" + (iframe.style.left) +" )";
|
||||
} else if (flip==2){
|
||||
iframe.style.transform += " scaleY(-1)";
|
||||
iframe.style.top = "calc(" + (iframe.style.top) +" )";
|
||||
iframe.style.left = "calc(" + (iframe.style.left) +" + 100vw)";
|
||||
// Create container for iframe if it doesn't exist
|
||||
var container = document.getElementById('iframe-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'iframe-container';
|
||||
container.style.overflow = 'hidden';
|
||||
container.style.position = 'absolute';
|
||||
container.style.top = menuOffset;
|
||||
container.style.left = '0';
|
||||
container.style.width = '100vw';
|
||||
container.style.height = 'calc(100vh - ' + menuOffset + ')';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
} else if (rotate==90){
|
||||
iframe.style.transform = "rotate(90deg)";
|
||||
iframe.style.width = "calc(100vh - "+menuOffset+")";
|
||||
iframe.style.height = "100vw";
|
||||
iframe.style.transformOrigin = "0 0;";
|
||||
iframe.style.left = "calc(100vw";
|
||||
iframe.style.top = menuOffset;
|
||||
|
||||
if (flip==1){
|
||||
iframe.style.transform += " scaleX(-1)";
|
||||
iframe.style.top = "calc(" + (iframe.style.top) +" - "+menuOffset+" + 100vh)";
|
||||
iframe.style.left = "calc(" + (iframe.style.left) +" )";
|
||||
} else if (flip==2){
|
||||
iframe.style.transform += " scaleY(-1)";
|
||||
iframe.style.top = "calc(" + (iframe.style.top) +" )";
|
||||
iframe.style.left = "calc(" + (iframe.style.left) +" - 100vw)";
|
||||
}
|
||||
// Reset iframe styles
|
||||
iframe.style.position = 'absolute';
|
||||
iframe.style.border = 'none';
|
||||
iframe.style.margin = '0';
|
||||
iframe.style.padding = '0';
|
||||
|
||||
// Calculate dimensions based on zoom
|
||||
if (rotate == '90' || rotate == '270') {
|
||||
// For rotated views, swap width and height
|
||||
iframe.style.width = (100 / zoomLevel) + 'vh';
|
||||
iframe.style.height = (100 / zoomLevel) + 'vw';
|
||||
} else {
|
||||
iframe.style.transform = "rotate(0deg)";
|
||||
iframe.style.width = "100vw";
|
||||
iframe.style.height = "calc(100vh - "+menuOffset+")";
|
||||
iframe.style.transformOrigin = "0 0;";
|
||||
iframe.style.position = "rotate(0deg)";
|
||||
iframe.style.left = "0";
|
||||
iframe.style.width = (100 / zoomLevel) + 'vw';
|
||||
iframe.style.height = 'calc(' + (100 / zoomLevel) + 'vh - ' + (parseInt(menuOffset) / zoomLevel) + 'px)';
|
||||
}
|
||||
|
||||
// Set transform origin
|
||||
iframe.style.transformOrigin = '0 0';
|
||||
|
||||
// Apply transformations
|
||||
var transforms = [];
|
||||
|
||||
// Add scale if not 1
|
||||
if (zoomLevel !== 1) {
|
||||
transforms.push('scale(' + zoomLevel + ')');
|
||||
}
|
||||
|
||||
// Add rotation
|
||||
if (rotate != '0') {
|
||||
transforms.push('rotate(' + rotate + 'deg)');
|
||||
}
|
||||
|
||||
// Add flipping
|
||||
if (flip == '1') {
|
||||
transforms.push('scaleX(-1)');
|
||||
} else if (flip == '2') {
|
||||
transforms.push('scaleY(-1)');
|
||||
}
|
||||
|
||||
// Set the transform
|
||||
iframe.style.transform = transforms.join(' ');
|
||||
|
||||
// Position the iframe based on rotation and scale
|
||||
if (rotate == '180') {
|
||||
iframe.style.top = 'calc(100vh - ' + menuOffset + ')';
|
||||
iframe.style.left = '100vw';
|
||||
} else if (rotate == '270') {
|
||||
iframe.style.top = '100vh';
|
||||
iframe.style.left = '0';
|
||||
} else if (rotate == '90') {
|
||||
iframe.style.top = menuOffset;
|
||||
|
||||
if (flip==1){
|
||||
iframe.style.transform += " scaleX(-1)";
|
||||
iframe.style.top = "calc(" + (iframe.style.top) +" )";
|
||||
iframe.style.left = "calc(" + (iframe.style.left) +" + 100vw)";
|
||||
} else if (flip==2){
|
||||
iframe.style.transform += " scaleY(-1)";
|
||||
iframe.style.top = "calc( 100vh)";
|
||||
|
||||
//iframe.style.left = "calc(" + (iframe.style.left) +" + 100vw)";
|
||||
}
|
||||
}
|
||||
iframe.style.left = '100vw';
|
||||
} else {
|
||||
iframe.style.top = menuOffset;
|
||||
iframe.style.left = '0';
|
||||
}
|
||||
|
||||
// Add the iframe to the container
|
||||
container.appendChild(iframe);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
88
tts.html
Normal file
88
tts.html
Normal file
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
|
||||
<meta content="utf-8" http-equiv="encoding" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Speech Synthesis Languages</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #333;
|
||||
color: #ddd;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
}
|
||||
#languageSelect, table {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #777;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background-color: #444;
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background-color: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Available TTS Language Options</h1>
|
||||
<select style='display:none;' id="languageSelect"></select>
|
||||
<table id="voicesTable">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Language</th>
|
||||
<th>Local Service</th>
|
||||
<th>Default</th>
|
||||
</tr>
|
||||
<!-- Rows will be populated here -->
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const select = document.getElementById('languageSelect');
|
||||
const table = document.getElementById('voicesTable');
|
||||
|
||||
function populateVoices() {
|
||||
const voices = speechSynthesis.getVoices();
|
||||
voices.forEach(voice => {
|
||||
const option = document.createElement('option');
|
||||
option.textContent = `${voice.name} (${voice.lang})`;
|
||||
select.appendChild(option);
|
||||
|
||||
const row = table.insertRow();
|
||||
row.insertCell().textContent = voice.name;
|
||||
row.insertCell().textContent = voice.lang;
|
||||
row.insertCell().textContent = voice.localService ? 'Yes' : 'No';
|
||||
row.insertCell().textContent = voice.default ? 'Yes' : 'No';
|
||||
});
|
||||
}
|
||||
|
||||
speechSynthesis.onvoiceschanged = populateVoices;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
71
whip.html
71
whip.html
@@ -706,11 +706,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="troubleshooting">
|
||||
<i class="las la-question-circle"></i>
|
||||
<span>Can't publish WHIP via OBS outside your LAN?</span>
|
||||
<a href="https://backup.vdo.ninja/OBS_VDO_Ninja.zip" target="_blank">Download our patched OBS version</a>
|
||||
<a href='https://github.com/steveseguin/obs-studio/' target="_blank">[source]</a>
|
||||
<div class="troubleshooting" style="background: rgba(255, 193, 7, 0.2); border-left: 4px solid #ffc107;">
|
||||
<i class="las la-exclamation-triangle" style="color: #ffc107; font-size: 1.3em;"></i>
|
||||
<span><strong>Can't publish WHIP via OBS outside your LAN?</strong></span>
|
||||
Download our patched OBS version:<a href="https://backup.vdo.ninja/OBS_VDO_Ninja.zip" target="_blank">[Windows]</a><a href="https://drive.google.com/file/d/1bDln_cOuAb3wA0fzvXwsY8WX1vZKGEIJ/view?usp=sharing" target="_blank">[macOS]</a><a href='https://github.com/steveseguin/obs-studio/' target="_blank">[source]</a>
|
||||
</div>
|
||||
|
||||
<!-- NEW SETTINGS BOX -->
|
||||
<div class="info-box" style="background: rgba(106, 171, 35, 0.15); border-radius: 10px; padding: 20px; margin: 20px 0; border-left: 4px solid #6aab23;">
|
||||
<h4 style="color: #6aab23; margin-top: 0; font-size: 1.1em;">📋 Recommended OBS WHIP Settings</h4>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 15px;">
|
||||
<div>
|
||||
<ul style="margin: 0; padding-left: 20px; color: white;">
|
||||
<li>Rate Control: <strong>CRF</strong></li>
|
||||
<li>CRF: <strong>23</strong></li>
|
||||
<li>Keyframe Interval: <strong>1s</strong></li>
|
||||
<li>Preset: <strong>Veryfast</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<ul style="margin: 0; padding-left: 20px; color: white;">
|
||||
<li>Profile: <strong>High</strong></li>
|
||||
<li>Tune: <strong>Fastdecode</strong> (required)</li>
|
||||
<li>x264 Options: <strong>bframes=0</strong> (required)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 15px; color: #ddd; font-size: 0.9em;">
|
||||
⚠️ <strong>Important:</strong> Using <code>&buffer=2500</code> in your view link can help reduce skipped frames at the cost of increased latency.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -927,6 +951,22 @@ document.querySelector("#changeText1a").value = localStorage.getItem('changeText
|
||||
document.querySelector("#changeText2").value = localStorage.getItem('changeText2') || "";
|
||||
|
||||
|
||||
const tabHashMap = {
|
||||
'urlInput1': 'publish',
|
||||
'urlInput2': 'obs',
|
||||
'urlInput1a': 'twitch',
|
||||
'urlInput3': 'play',
|
||||
'urlInput4': 'host'
|
||||
};
|
||||
|
||||
const hashTabMap = {
|
||||
'publish': 'urlInput1',
|
||||
'obs': 'urlInput2',
|
||||
'twitch': 'urlInput1a',
|
||||
'play': 'urlInput3',
|
||||
'host': 'urlInput4'
|
||||
};
|
||||
|
||||
if (localStorage.getItem('changeText3')!==null){
|
||||
document.getElementById('changeText3').value = localStorage.getItem('changeText3');
|
||||
}
|
||||
@@ -1132,7 +1172,7 @@ function gohere1t(){
|
||||
function gohere2(){
|
||||
if (document.getElementById('changeText2').value){
|
||||
localStorage.setItem('changeText2', document.getElementById('changeText2').value);
|
||||
window.location = domain + "?hidemenu&whip=" + document.getElementById('changeText2').value;
|
||||
window.location = domain + "?whip=" + document.getElementById('changeText2').value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1177,7 +1217,7 @@ function gohere3(){
|
||||
|
||||
addedon += "&whepwait="+document.getElementById('whepicewait').value;
|
||||
|
||||
window.location = domain + "?&hidemenu&whepplay=" + encodeURIComponent(document.getElementById('changeText3').value)+addedon;
|
||||
window.location = domain + "?&whepplay=" + encodeURIComponent(document.getElementById('changeText3').value)+addedon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1407,8 +1447,18 @@ function switchTab(targetId) {
|
||||
tab.classList.toggle('active', index === Array.from(document.querySelectorAll('.urlInput'))
|
||||
.findIndex(section => section.id === targetId));
|
||||
});
|
||||
|
||||
window.location.hash = tabHashMap[targetId];
|
||||
}
|
||||
|
||||
function checkHashAndSelectTab() {
|
||||
const hash = window.location.hash.substring(1);
|
||||
if (hash && hashTabMap[hash]) {
|
||||
switchTab(hashTabMap[hash]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function toggleAdvanced(section, toggle) {
|
||||
const isHidden = !section.classList.contains('visible');
|
||||
section.classList.toggle('visible');
|
||||
@@ -1437,7 +1487,12 @@ function generateNewToken() {
|
||||
}
|
||||
|
||||
// Initialize UI when page loads
|
||||
document.addEventListener('DOMContentLoaded', initializeUI);
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeUI();
|
||||
checkHashAndSelectTab();
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', checkHashAndSelectTab);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user