// ==UserScript==
// @name YouTube 30 FPS Forzado
// @name:en YouTube 30 FPS Forced
// @namespace AkioBrian
// @version 2.0.0
// @description Fuerza YouTube a reproducir videos a 30 FPS máximo, reduciendo el uso de CPU y mejorando el rendimiento en equipos antiguos
// @description:en Forces YouTube to play videos at a maximum of 30 FPS, reducing CPU usage and improving performance on older devices.
// @icon https://i.imgur.com/oM51Lex.png
// @author SteveJobzniak, AkioBrian
// @license https://www.apache.org/licenses/LICENSE-2.0
// @match *://www.youtube.com/*
// @exclude *://www.youtube.com/tv*
// @exclude *://www.youtube.com/embed/*
// @run-at document-start
// @grant none
// @noframes
// ==/UserScript==
(function() {
'use strict';
// Configuración
const CONFIG = {
DEBUG: false,
MAX_FPS: 30,
MAX_REINTENTOS: 2,
TIMEOUT_VERIFICACION: 8000
};
// Crear verificador de formatos personalizado
const crearVerificadorFormatos = (verificadorOriginal, callback) => {
return function(tipoVideo) {
if (CONFIG.DEBUG) {
console.log(`[YouTube 30FPS] Consulta: "${tipoVideo}"`);
}
callback?.('query', tipoVideo);
if (!tipoVideo) return false;
// Bloquear formatos de alta frecuencia
const coincidencias = tipoVideo.match(/framerate=(\d+)/);
if (coincidencias) {
const fps = parseInt(coincidencias[1], 10);
if (fps > CONFIG.MAX_FPS) {
if (CONFIG.DEBUG) {
console.log(`[YouTube 30FPS] Bloqueado: ${fps}fps`);
}
callback?.('block', tipoVideo);
return false;
}
}
return verificadorOriginal(tipoVideo);
};
};
// Verificar si estamos en una página de video
const esPaginaVideo = () => location.pathname === '/watch';
// Obtener contador de recargas de la URL
const obtenerContadorRecargas = () => {
if (!location.hash) return null;
const coincidencias = location.hash.match(/fpsreloads=(\d+)/);
return coincidencias ? parseInt(coincidencias[1], 10) : null;
};
// Establecer contador y recargar página
const establecerContadorYRecargar = (nuevoContador) => {
let hashAntiguo = location.hash.replace(/^#/, '').replace(/&?fpsreloads=\d+/, '');
let hashNuevo = hashAntiguo ? `${hashAntiguo}&fpsreloads=${nuevoContador}` : `fpsreloads=${nuevoContador}`;
location.hash = hashNuevo;
location.reload();
};
// Eliminar contador de recargas de la URL
const eliminarContadorRecargas = () => {
if (window.history?.replaceState) {
const nuevaUrl = location.href.replace(/(#?)fpsreloads=\d+&?/g, '$1').replace(/[#&]+$/, '');
if (location.href !== nuevaUrl) {
window.history.replaceState({}, document.title, nuevaUrl);
}
}
};
// Manejar reintentos de inyección
const manejarReintentos = (exitoso, recargasActuales) => {
if (exitoso || !esPaginaVideo()) {
eliminarContadorRecargas();
if (CONFIG.DEBUG) {
console.log('[YouTube 30FPS] Inyección exitosa');
}
return;
}
const siguienteRecarga = (recargasActuales ?? 0) + 1;
if (siguienteRecarga <= CONFIG.MAX_REINTENTOS) {
if (CONFIG.DEBUG) {
console.log(`[YouTube 30FPS] Reintento ${siguienteRecarga}/${CONFIG.MAX_REINTENTOS}`);
}
establecerContadorYRecargar(siguienteRecarga);
} else {
eliminarContadorRecargas();
mostrarMensajeError();
}
};
// Mostrar mensaje de error con auto-ocultado
const mostrarMensajeError = () => {
const crearDivError = () => {
if (!document.body || document.getElementById('youtube-30fps-error')) {
return false;
}
const divError = document.createElement('div');
divError.id = 'youtube-30fps-error';
Object.assign(divError.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
maxWidth: '350px',
padding: '15px',
borderRadius: '8px',
fontSize: '14px',
color: '#fff',
backgroundColor: 'rgba(244, 67, 54, 0.95)',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
zIndex: '99999',
fontFamily: 'Arial, sans-serif',
lineHeight: '1.4'
});
divError.innerHTML = `
<div style="font-weight: bold; margin-bottom: 8px;">⚠️ YouTube 30 FPS</div>
<div style="margin-bottom: 10px;">No se pudo limitar a 30 FPS en esta pestaña.</div>
<div style="font-size: 12px;">
<a href="#" onclick="location.reload(); return false"
style="color: #ffcdd2; text-decoration: underline;">Recargar página</a> •
<a href="#" onclick="this.parentElement.parentElement.parentElement.remove(); return false"
style="color: #ffcdd2; text-decoration: underline;">Cerrar</a>
</div>
`;
document.body.appendChild(divError);
// Auto-ocultar después de 10 segundos
setTimeout(() => {
if (divError.parentNode) {
divError.style.transition = 'opacity 0.5s ease-out';
divError.style.opacity = '0';
setTimeout(() => divError.remove(), 500);
}
}, 10000);
return true;
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', crearDivError);
} else {
crearDivError();
}
};
// Verificar soporte para MediaSource
if (!window.MediaSource) {
console.warn('MediaSource no soportado en este navegador');
return;
}
const recargasActuales = obtenerContadorRecargas();
// Verificar si la inyección fue demasiado tardía
const inyeccionTardia = window.ytplayer && Object.getOwnPropertyNames(window.ytplayer).length > 0;
if (esPaginaVideo() && inyeccionTardia) {
manejarReintentos(false, recargasActuales);
return;
}
// Limpiar contador de recargas si la inyección parece exitosa
eliminarContadorRecargas();
// Configurar verificación de éxito
let contadorBloqueos = 0;
const actualizarContadorBloqueos = (accion) => {
if (accion === 'block') contadorBloqueos++;
};
const verificarExito = () => {
const exitoso = contadorBloqueos >= 1;
manejarReintentos(exitoso, recargasActuales);
};
// Configurar timer de verificación para páginas de video
if (esPaginaVideo()) {
setTimeout(verificarExito, CONFIG.TIMEOUT_VERIFICACION);
} else {
manejarReintentos(true, recargasActuales);
}
// Inyectar el bloqueador de formatos de video
try {
const verificadorOriginal = window.MediaSource.isTypeSupported.bind(window.MediaSource);
window.MediaSource.isTypeSupported = crearVerificadorFormatos(verificadorOriginal, actualizarContadorBloqueos);
if (CONFIG.DEBUG) {
console.log('[YouTube 30FPS] Script inyectado correctamente');
}
} catch (error) {
console.error('[YouTube 30FPS] Error al inyectar:', error);
}
})();