UnsafeYT Decoder

A script to process visually and auditory scrambled YouTube videos into a human understandable format, but slightly more optimized. Now also decoding hover previews. Includes an aggressive audio compressor to limit loud noises.

Fra 16.09.2025. Se den seneste versjonen.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name            UnsafeYT Decoder
// @namespace       unsafe-yt-decoder-namespace
// @version         0.9.3
// @match           https://www.youtube.com/*
// @match           https://m.youtube.com/*
// @match           *://www.youtube-nocookie.com/*
// @exclude         *://www.youtube.com/live_chat*
// @grant           none
// @run-at          document-idle
// @inject-into     page
// @license         MIT
// @description     A script to process visually and auditory scrambled YouTube videos into a human understandable format, but slightly more optimized. Now also decoding hover previews. Includes an aggressive audio compressor to limit loud noises.
// ==/UserScript==

/*jshint esversion: 11 */

(function () {
    'use strict';

    /************************************************************************
     * SECTION A — CONFIG & SHADERS
     ************************************************************************/

    const VERT_SHADER_SRC = `#version 300 es
    in vec2 a_position; in vec2 a_texCoord; out vec2 v_texCoord;
    void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texCoord = a_texCoord; }`;

    const FRAG_SHADER_SRC = `#version 300 es
    precision highp float;
    in vec2 v_texCoord; out vec4 fragColor;
    uniform sampler2D u_sampler; uniform sampler2D u_shuffle;

    const float PI = 3.14159265359;

    vec4 getColor( vec2 uv ){
        vec2 uv_clamped = clamp(uv, 0.0, 1.0);
        vec2 shuffle_sample = texture(u_shuffle, uv_clamped).rg;
        vec2 final_sample_pos = uv + shuffle_sample;
        vec4 c = texture(u_sampler, final_sample_pos);
        return vec4(1.0 - c.rgb, c.a);
    }

    vec2 getNormal(vec2 uv){vec2 o=vec2(0.0065);vec2 c=round((uv+o)*80.)/80.;return(c-(uv+o))*80.;}
    float getAxis(vec2 uv){vec2 n=getNormal(uv);float a=abs(n.x)>0.435?1.:0.;return abs(n.y)>0.4?2.:a;}
    float getGrid(vec2 uv){return getAxis(uv)>0.?1.:0.;}
    vec4 getGridFix(vec2 uv){vec2 n=getNormal(uv);vec4 b=getColor(uv);vec4 o=getColor(uv+n*0.002);float g=getGrid(uv);return mix(b,o,g);}

    vec4 getSmoothed( vec2 uv, float power, float slice ){
        vec4 totalColor = vec4(0.0);
        float totalWeight = 0.0;
        const float sigma = 0.45;
        const int sampleCount = 16;
        vec2 samples[16]=vec2[](vec2(-.326,-.405),vec2(-.840,-.073),vec2(-.695,.457),vec2(-.203,.620),vec2(.962,-.194),vec2(.473,-.480),vec2(.519,.767),vec2(.185,-.893),vec2(.507,.064),vec2(.896,.412),vec2(-.321,.932),vec2(-.791,-.597),vec2(.089,.290),vec2(.354,-.215),vec2(-.825,.223),vec2(-.913,-.281));

        for(int i = 0; i < sampleCount; i++){
            vec2 offset = samples[i] * power;
            float dist = length(samples[i]);
            float weight = exp(-(dist * dist) / (2.0 * sigma * sigma));
            totalColor += getGridFix(uv + offset) * weight;
            totalWeight += weight;
        }
        return totalColor / totalWeight;
    }

    void main() {
        vec2 uv=vec2(v_texCoord.x,1.-v_texCoord.y);
        float a=getAxis(uv),g=a>0.?1.:0.;
        float s[3]=float[3](0.,0.,PI);
        vec4 m=getGridFix(uv),o=getSmoothed(uv,0.0008,s[int(a)]);
        m=mix(m,o,g);
        fragColor = m;
    }`;

    /************************************************************************
     * SECTION B — GLOBAL STATE & HELPERS
     ************************************************************************/

    const initialState = () => ({
        token: '',
        isRendering: false,
        canvas: null,
        gl: null,
        audio: { context: null, sourceNode: null, gainNode: null, compressor: null, outputGainNode: null, notchFilters: [] },
        renderFrameId: null,
        originalContainerStyle: null,
        resizeObserver: null,
        listenerController: null, // For cleaning up event listeners
        moviePlayer: null,
    });
    let state = initialState();
    let isApplyingEffects = false; // The state lock

    let userscriptHTMLPolicy;
    function createTrustedHTML(html) {
        if (window.trustedTypes && window.trustedTypes.createPolicy) {
            if (!userscriptHTMLPolicy) {
                userscriptHTMLPolicy = window.trustedTypes.createPolicy('userscript-html-policy', { createHTML: (s) => s });
            }
            return userscriptHTMLPolicy.createHTML(html);
        }
        return html;
    }

    /************************************************************************
     * SECTION C, D, E — UTILITIES
     ************************************************************************/

    function deterministicHash(s, prime = 31, modulus = Math.pow(2, 32)) {
        let h = 0;
        modulus = Math.floor(modulus);
        for (let i = 0; i < s.length; i++) {
            const charCode = s.charCodeAt(i);
            h = (h * prime + charCode) % modulus;
            if (h < 0) {
                h += modulus;
            }
        }
        return h / modulus;
    }

    function _generateUnshuffleOffsetMapFloat32Array(seedToken, width, height) {
        if (!seedToken || width <= 0 || height <= 0) {
            throw new Error('Invalid params for unshuffle map.');
        }
        const totalPixels = width * height;
        const startHash = deterministicHash(seedToken, 31, 2 ** 32 - 1);
        const stepHash = deterministicHash(seedToken + '_step', 37, 2 ** 32 - 2);
        const startAngle = startHash * Math.PI * 2.0;
        const angleIncrement = (stepHash * Math.PI) / Math.max(width, height);
        const indexedValues = Array.from({ length: totalPixels }, (_, i) => ({
            value: Math.sin(startAngle + i * angleIncrement),
            index: i,
        }));
        indexedValues.sort((a, b) => a.value - b.value);
        const pLinearized = new Array(totalPixels);
        for (let k = 0; k < totalPixels; k++) {
            pLinearized[indexedValues[k].index] = k;
        }
        const offsetMapFloats = new Float32Array(totalPixels * 2);
        for (let oy = 0; oy < height; oy++) {
            for (let ox = 0; ox < width; ox++) {
                const originalLinearIndex = oy * width + ox;
                const shuffledLinearIndex = pLinearized[originalLinearIndex];
                const sy_shuffled = Math.floor(shuffledLinearIndex / width);
                const sx_shuffled = shuffledLinearIndex % width;
                const offsetX = (sx_shuffled - ox) / width;
                const offsetY = (sy_shuffled - oy) / height;
                const pixelDataIndex = (oy * width + ox) * 2;
                offsetMapFloats[pixelDataIndex] = offsetX;
                offsetMapFloats[pixelDataIndex + 1] = offsetY;
            }
        }
        return offsetMapFloats;
    }

    function extractTokenFromText(text) {
        try {
            if (!text) return '';
            const trimmed = text.trim();
            const firstLine = trimmed.split(/\r?\n/)[0] || '';
            const keyMarkers = ['token:', 'key:'];
            let key = '';
            keyMarkers.forEach((marker) => {
                if (firstLine.toLowerCase().startsWith(marker)) {
                    key = firstLine.substring(marker.length).trim();
                    return;
                }
            });
            return key;
        } catch (t) {
            console.error('[UnsafeYT] Token extraction error:', t);
        }
    }

    function injectStyles() {
        if (document.getElementById('unsafeyt-styles')) return;
        const STYLES = ` #unsafeyt-controls { display: flex; gap: 8px; align-items: center; margin-left: 12px; } .unsafeyt-button { background: transparent; color: white; padding: 6px 8px; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 600; outline: none; transition: box-shadow .2s, border-color .2s; } #unsafeyt-toggle { border: 2px solid rgba(200,0,0,0.95); } #unsafeyt-toggle.active { border-color: rgba(0,200,0,0.95); box-shadow: 0 0 8px rgba(0,200,0,0.25); } #unsafeyt-manual { border: 1px solid rgba(255,255,255,0.2); } #unsafeyt-token-indicator { width: 10px; height: 10px; border-radius: 50%; margin-left: 6px; background: transparent; } #unsafeyt-token-indicator.present { background: limegreen; } `;
        const styleSheet = document.createElement('style');
        styleSheet.id = 'unsafeyt-styles';
        styleSheet.innerHTML = createTrustedHTML(STYLES);
        document.head.appendChild(styleSheet);
    }

    function createControlButtons() {
        try {
            if (window.location.pathname !== '/watch' || document.querySelector('#unsafeyt-controls')) return;
            injectStyles();
            const bar = document.querySelector('#top-level-buttons-computed');
            if (!bar) throw new Error('Top-level buttons not found.');
            const buttonContainer = document.createElement('div');
            buttonContainer.id = 'unsafeyt-controls';
            const buttonHTML = `<button id="unsafeyt-toggle" type="button" class="unsafeyt-button">Toggle Effects</button><button id="unsafeyt-manual" type="button" class="unsafeyt-button">Enter Token</button><div id="unsafeyt-token-indicator" title="Token presence"></div>`;
            buttonContainer.innerHTML = createTrustedHTML(buttonHTML);
            bar.insertBefore(buttonContainer, bar.firstChild);
            buttonContainer.querySelector('#unsafeyt-toggle').addEventListener('click', async () => {
                if (state.isRendering) {
                    removeEffects();
                } else {
                    if (!state.token) {
                        const manual = prompt('No token auto-detected. Enter token manually:');
                        if (!manual) return;
                        state.token = manual.trim();
                    }
                    await applyEffects(state.token);
                }
            });
            buttonContainer.querySelector('#unsafeyt-manual').addEventListener('click', () => {
                const v = prompt("Enter token (first line of description can also be 'token:...'):");
                if (v?.trim()) {
                    state.token = v.trim();
                    applyEffects(state.token);
                }
            });
            updateUIState();
        } catch (error) {
            console.error('[UnsafeYT] Error creating control buttons:', error);
        }
    }

    function updateUIState() {
        const toggle = document.querySelector('#unsafeyt-toggle');
        const indicator = document.querySelector('#unsafeyt-token-indicator');
        if (toggle) toggle.classList.toggle('active', state.isRendering);
        if (indicator) indicator.classList.toggle('present', !!state.token);
    }

    /************************************************************************
     * SECTION F — CLEANUP
     ************************************************************************/

    async function removeEffects() {
        if (isApplyingEffects) {
            console.warn('[UnsafeYT] State transition in progress, ignoring remove request.');
            return;
        }
        if (!state.isRendering && !state.canvas) {
            return;
        }

        isApplyingEffects = true;
        try {
            if (state.listenerController) {
                state.listenerController.abort();
            }
            if (state.audio.context && state.audio.context.state !== 'closed') {
                try {
                    await state.audio.context.close();
                } catch (t) {}
            }
            state.isRendering = false;
            if (state.canvas) {
                try {
                    state.canvas.remove();
                } catch (t) {}
            }
            if (state.renderFrameId !== null) cancelAnimationFrame(state.renderFrameId);
            if (state.resizeObserver) state.resizeObserver.disconnect();
            if (state.gl) {
                try {
                    const t = state.gl.getExtension('WEBGL_lose_context');
                    if (t) t.loseContext();
                } catch (t) {}
            }
            const container = document.querySelector('.html5-video-container');
            if (container && state.originalContainerStyle) {
                try {
                    Object.assign(container.style, state.originalContainerStyle);
                } catch (t) {}
            }

            if (state.audio.context) {
                Object.values(state.audio).forEach((node) => {
                    if (node?.disconnect)
                        try {
                            node.disconnect();
                        } catch (e) {}
                });
                const video = document.querySelector('.video-stream');
                if (video) {
                    try {
                        video.style.opacity = '1';
                        const currentSrc = video.src;
                        video.src = '';
                        video.load();
                        video.src = currentSrc;
                        video.load();
                    } catch (t) {}
                }
            }

            state = { ...initialState(), token: state.token };
            updateUIState();
            console.log('[UnsafeYT] Removed applied effects.');
        } finally {
            isApplyingEffects = false;
        }
    }

    /************************************************************************
     * SECTION G — CORE
     ************************************************************************/

    async function applyEffects(seedToken, playerContainer = null, videoElement = null) {
        if (isApplyingEffects) {
            console.warn('[UnsafeYT] Apply effects is already in progress. Ignoring request.');
            return;
        }
        isApplyingEffects = true;

        try {
            await removeEffects();

            if (typeof seedToken !== 'string' || seedToken.length < 3) {
                return;
            }
            state.token = seedToken;
            console.log(`[UnsafeYT] Applying effects with token: "${state.token}"`);

            const video = videoElement ?? document.querySelector('.video-stream');
            const container = playerContainer?.querySelector('.html5-video-container') ?? document.querySelector('.html5-video-container');
            if (!video || !container) {
                return;
            }
            video.style.opacity = '0';
            video.crossOrigin = 'anonymous';

            state.canvas = document.createElement('canvas');
            state.canvas.id = 'unsafeyt-glcanvas';
            Object.assign(state.canvas.style, {
                position: 'absolute',
                top: `${location.href.includes('m.youtube') ? '50%' : '0%'}`,
                left: '50%',
                transform: 'translateY(0%) translateX(-50%)',
                pointerEvents: 'none',
                zIndex: 12,
                touchAction: 'none',
            });
            if (!state.originalContainerStyle)
                state.originalContainerStyle = { position: container.style.position, height: container.style.height };
            Object.assign(container.style, { position: 'relative', height: '100%' });
            container.appendChild(state.canvas);

            state.gl = state.canvas.getContext('webgl2', { alpha: false }) || state.canvas.getContext('webgl', { alpha: false });
            if (!state.gl) {
                await removeEffects();
                return;
            }

            let oesTextureFloatExt = null;
            if (state.gl instanceof WebGLRenderingContext) {
                oesTextureFloatExt = state.gl.getExtension('OES_texture_float');
            }

            const resizeCallback = () => {
                if (!state.canvas || !video) return;
                state.canvas.width = video.offsetWidth || video.videoWidth || 640;
                state.canvas.height = video.offsetHeight || video.videoHeight || 360;
                if (state.gl) {
                    try {
                        state.gl.viewport(0, 0, state.gl.drawingBufferWidth, state.gl.drawingBufferHeight);
                    } catch (t) {}
                }
            };
            state.resizeObserver = new ResizeObserver(resizeCallback);
            state.resizeObserver.observe(video);
            resizeCallback();

            function compileShader(type, src) {
                try {
                    if (!state.gl) return null;
                    const shader = state.gl.createShader(type);
                    if (!shader) throw new Error('Failed to create shader.');
                    state.gl.shaderSource(shader, src);
                    state.gl.compileShader(shader);
                    if (!state.gl.getShaderParameter(shader, state.gl.COMPILE_STATUS)) {
                        state.gl.deleteShader(shader);
                        throw new Error(state.gl.getShaderInfoLog(shader));
                    }
                    return shader;
                } catch (t) {
                    return null;
                }
            }
            function createProgram(vsSrc, fsSrc) {
                try {
                    if (!state.gl) return null;
                    const vs = compileShader(state.gl.VERTEX_SHADER, vsSrc);
                    const fs = compileShader(state.gl.FRAGMENT_SHADER, fsSrc);
                    if (!vs || !fs) throw new Error('Shader creation failed.');
                    const program = state.gl.createProgram();
                    state.gl.attachShader(program, vs);
                    state.gl.attachShader(program, fs);
                    state.gl.linkProgram(program);
                    if (!state.gl.getProgramParameter(program, state.gl.LINK_STATUS)) {
                        try {
                            state.gl.deleteProgram(program);
                        } catch (t) {}
                        try {
                            state.gl.deleteShader(vs);
                            state.gl.deleteShader(fs);
                        } catch (t) {}
                        throw new Error('Program link error:' + state.gl.getProgramInfoLog(program));
                    }
                    state.gl.useProgram(program);
                    try {
                        state.gl.deleteShader(vs);
                        state.gl.deleteShader(fs);
                    } catch (t) {}
                    return program;
                } catch (t) {
                    return null;
                }
            }

            try {
                const program = createProgram(VERT_SHADER_SRC, FRAG_SHADER_SRC);
                if (!program) {
                    await removeEffects();
                    return;
                }
                const posLoc = state.gl.getAttribLocation(program, 'a_position');
                const texLoc = state.gl.getAttribLocation(program, 'a_texCoord');
                const videoSamplerLoc = state.gl.getUniformLocation(program, 'u_sampler');
                const shuffleSamplerLoc = state.gl.getUniformLocation(program, 'u_shuffle');
                const quadVerts = new Float32Array([-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, -1, 1, 0, 1, 1, -1, 1, 0, 1, 1, 1, 1]);
                const buf = state.gl.createBuffer();
                state.gl.bindBuffer(state.gl.ARRAY_BUFFER, buf);
                state.gl.bufferData(state.gl.ARRAY_BUFFER, quadVerts, state.gl.STATIC_DRAW);
                state.gl.enableVertexAttribArray(posLoc);
                state.gl.vertexAttribPointer(posLoc, 2, state.gl.FLOAT, false, 16, 0);
                state.gl.enableVertexAttribArray(texLoc);
                state.gl.vertexAttribPointer(texLoc, 2, state.gl.FLOAT, false, 16, 8);
                const videoTex = state.gl.createTexture();
                state.gl.bindTexture(state.gl.TEXTURE_2D, videoTex);
                state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_WRAP_S, state.gl.CLAMP_TO_EDGE);
                state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_WRAP_T, state.gl.CLAMP_TO_EDGE);
                state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MIN_FILTER, state.gl.LINEAR);
                state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MAG_FILTER, state.gl.LINEAR);
                let unshuffleMapFloats = null;
                try {
                    unshuffleMapFloats = _generateUnshuffleOffsetMapFloat32Array(state.token, 80, 80);
                } catch (t) {
                    await removeEffects();
                    return;
                }
                const shuffleTex = state.gl.createTexture();
                state.gl.activeTexture(state.gl.TEXTURE1);
                state.gl.bindTexture(state.gl.TEXTURE_2D, shuffleTex);
                state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_WRAP_S, state.gl.CLAMP_TO_EDGE);
                state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_WRAP_T, state.gl.CLAMP_TO_EDGE);
                state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MIN_FILTER, state.gl.NEAREST);
                state.gl.texParameteri(state.gl.TEXTURE_2D, state.gl.TEXTURE_MAG_FILTER, state.gl.NEAREST);
                if (state.gl instanceof WebGL2RenderingContext) {
                    try {
                        state.gl.texImage2D(
                            state.gl.TEXTURE_2D,
                            0,
                            state.gl.RG32F,
                            80,
                            80,
                            0,
                            state.gl.RG,
                            state.gl.FLOAT,
                            unshuffleMapFloats,
                        );
                    } catch (t) {
                        try {
                            const p = new Float32Array(80 * 80 * 4);
                            for (let i = 0; i < unshuffleMapFloats.length / 2; i++) {
                                p[i * 4] = unshuffleMapFloats[i * 2];
                                p[i * 4 + 1] = unshuffleMapFloats[i * 2 + 1];
                            }
                            state.gl.texImage2D(state.gl.TEXTURE_2D, 0, state.gl.RGBA32F, 80, 80, 0, state.gl.RGBA, state.gl.FLOAT, p);
                        } catch (t) {
                            await removeEffects();
                            return;
                        }
                    }
                } else if (oesTextureFloatExt) {
                    try {
                        const p = new Float32Array(80 * 80 * 4);
                        for (let i = 0; i < unshuffleMapFloats.length / 2; i++) {
                            p[i * 4] = unshuffleMapFloats[i * 2];
                            p[i * 4 + 1] = unshuffleMapFloats[i * 2 + 1];
                        }
                        state.gl.texImage2D(state.gl.TEXTURE_2D, 0, state.gl.RGBA, 80, 80, 0, state.gl.RGBA, state.gl.FLOAT, p);
                    } catch (t) {
                        await removeEffects();
                        return;
                    }
                } else {
                    await removeEffects();
                    return;
                }
                state.gl.clearColor(0, 0, 0, 1);
                state.isRendering = true;
                const render = () => {
                    if (!state.isRendering || !state.gl || !video || !state.canvas) return;
                    if (video.readyState >= video.HAVE_CURRENT_DATA) {
                        state.gl.activeTexture(state.gl.TEXTURE0);
                        state.gl.bindTexture(state.gl.TEXTURE_2D, videoTex);
                        try {
                            state.gl.texImage2D(state.gl.TEXTURE_2D, 0, state.gl.RGBA, state.gl.RGBA, state.gl.UNSIGNED_BYTE, video);
                        } catch (t) {
                            try {
                                state.gl.texImage2D(
                                    state.gl.TEXTURE_2D,
                                    0,
                                    state.gl.RGBA,
                                    video.videoWidth,
                                    video.videoHeight,
                                    0,
                                    state.gl.RGBA,
                                    state.gl.UNSIGNED_BYTE,
                                    null,
                                );
                            } catch (t) {}
                        }
                        state.gl.uniform1i(videoSamplerLoc, 0);
                        state.gl.uniform1i(shuffleSamplerLoc, 1);
                        state.gl.clear(state.gl.COLOR_BUFFER_BIT);
                        state.gl.drawArrays(state.gl.TRIANGLES, 0, 6);
                    }
                    state.renderFrameId = requestAnimationFrame(render);
                };
                render();
            } catch (t) {
                await removeEffects();
                return;
            }
            try {
                const AudioCtx = window.AudioContext || window.webkitAudioContext;
                if (!AudioCtx) {
                } else {
                    if (!state.audio.context) state.audio.context = new AudioCtx();
                    const videoEl = document.querySelector('.video-stream');
                    if (videoEl) {
                        try {
                            if (!state.audio.sourceNode) state.audio.sourceNode = state.audio.context.createMediaElementSource(videoEl);
                        } catch (t) {
                            state.audio.sourceNode = null;
                        }
                        const splitter = state.audio.context.createChannelSplitter(2),
                            leftGain = state.audio.context.createGain(),
                            rightGain = state.audio.context.createGain(),
                            merger = state.audio.context.createChannelMerger(1);
                        leftGain.gain.value = 0.25;
                        rightGain.gain.value = 0.25;
                        state.audio.gainNode = state.audio.context.createGain();
                        state.audio.gainNode.gain.value = 1.0;
                        state.audio.compressor = state.audio.context.createDynamicsCompressor();
                        state.audio.compressor.threshold.value = -72;
                        state.audio.compressor.knee.value = 35;
                        state.audio.compressor.ratio.value = 15;
                        state.audio.compressor.attack.value = 0.003;
                        state.audio.compressor.release.value = 0.25;
                        state.audio.outputGainNode = state.audio.context.createGain();
                        state.audio.outputGainNode.gain.value = 4.0;
                        const fConfigs = [
                            { f: 200, q: 3, g: 1 },
                            { f: 440, q: 2, g: 1 },
                            { f: 6600, q: 1, g: 0 },
                            { f: 15600, q: 1, g: 0 },
                            { f: 5000, q: 20, g: 1 },
                            { f: 6000, q: 20, g: 1 },
                            { f: 6300, q: 5, g: 1 },
                            { f: 8000, q: 40, g: 1 },
                            { f: 10000, q: 40, g: 1 },
                            { f: 12500, q: 40, g: 1 },
                            { f: 14000, q: 40, g: 1 },
                            { f: 15000, q: 40, g: 1 },
                            { f: 15500, q: 1, g: 0 },
                            { f: 15900, q: 1, g: 0 },
                            { f: 16000, q: 40, g: 1 },
                        ];
                        state.audio.notchFilters = fConfigs.map((c) => {
                            const f = state.audio.context.createBiquadFilter();
                            f.type = 'notch';
                            f.frequency.value = c.f;
                            f.Q.value = c.q * 3.5;
                            f.gain.value = c.g;
                            return f;
                        });
                        if (state.audio.sourceNode) {
                            state.audio.sourceNode.connect(splitter);
                            splitter.connect(leftGain, 0);
                            splitter.connect(rightGain, 1);
                            leftGain.connect(merger, 0, 0);
                            rightGain.connect(merger, 0, 0);
                            const audioChain = [
                                merger,
                                state.audio.gainNode,
                                ...state.audio.notchFilters,
                                state.audio.compressor,
                                state.audio.outputGainNode,
                                state.audio.context.destination,
                            ];
                            audioChain.reduce((prev, next) => prev.connect(next));
                        }

                        state.listenerController = new AbortController();
                        const { signal } = state.listenerController;

                        const handleAudioState = async () => {
                            if (!state.audio.context || state.audio.context.state === 'closed') return;
                            if (videoEl.paused) {
                                if (state.audio.context.state === 'running') state.audio.context.suspend().catch(() => {});
                            } else {
                                if (state.audio.context.state === 'suspended') state.audio.context.resume().catch(() => {});
                            }
                        };
                        videoEl.addEventListener('play', handleAudioState, { signal });
                        videoEl.addEventListener('pause', handleAudioState, { signal });
                        if (!videoEl.paused) handleAudioState();
                    }
                }
            } catch (t) {}

            updateUIState();
            console.log('[UnsafeYT] Effects applied.');
        } finally {
            isApplyingEffects = false;
        }
    }

    /************************************************************************
     * SECTION H — Initialization / Observers
     ************************************************************************/

    function fallbackGetPlayer() {
        if (window.location.pathname.startsWith('/shorts')) {
            return document.querySelector('#shorts-player');
        } else if (window.location.pathname.startsWith('/watch')) {
            return document.querySelector('#movie_player');
        } else {
            return document.querySelector('.inline-preview-player');
        }
    }

    async function processVideo(playerContainer, playerApi, videoElement) {
        try {
            const newToken = extractTokenFromText(playerApi.getPlayerResponse()?.videoDetails?.shortDescription);
            if (newToken === state.token && (state.isRendering || !newToken) && state.moviePlayer === playerApi) {
                console.log('[UnsafeYT] No new token detected.');
                return;
            }
            console.log('[UnsafeYT] New video or token detected.');
            state.moviePlayer = playerApi;
            state.token = newToken;
            if (state.token) {
                videoElement.addEventListener(
                    'timeupdate',
                    async () => {
                        await applyEffects(state.token, playerContainer, videoElement);
                    },
                    { once: true },
                );
            } else if (state.isRendering) {
                await removeEffects();
            }
            updateUIState();
        } catch (t) {}
    }

    function handlePlayerUpdate(event) {
        console.log('handlePlayerUpdate');
        const useFallback = !event?.target?.player_;
        let playerContainer = event?.target;
        let playerApi = playerContainer?.player_;
        if (useFallback) {
            playerApi = fallbackGetPlayer();
            playerContainer = playerApi.parentElement;
        }

        const videoElement = playerContainer?.querySelector('video');
        if (videoElement && playerApi) {
            processVideo(playerContainer, playerApi, videoElement);
        }
    }

    function handleInitialLoad() {
        createControlButtons();
        let playerContainer = fallbackGetPlayer();
        if (playerContainer) {
            const videoElement = playerContainer.querySelector('video');
            const playerApi = playerContainer.player_ || playerContainer;
            if (videoElement && playerApi) {
                processVideo(playerContainer, playerApi, videoElement);
            }
        }
    }

    function init() {
        const playerUpdateEvent = window.location.hostname === 'm.youtube.com' ? 'state-navigateend' : 'yt-player-updated';
        handleInitialLoad();
        window.addEventListener(playerUpdateEvent, handlePlayerUpdate);
        window.addEventListener('yt-page-data-updated', createControlButtons);
        window.addEventListener('yt-watch-masthead-scroll', createControlButtons);
    }

    window.addEventListener('pageshow', init);
})();