Greasy Fork

Greasy Fork is available in English.

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.

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