YouTube 30 FPS Forzado

Fuerza YouTube a reproducir videos a 30 FPS máximo, reduciendo el uso de CPU y mejorando el rendimiento en equipos antiguos

// ==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);
    }
})();