YouTube Embed Adblock

Block ads with identical embed player

// ==UserScript==
// @name         YouTube Embed Adblock
// @namespace    https://github.com/Sillylittleguy1/Youtube-embed-adblock
// @version      2.4
// @description  Block ads with identical embed player
// @license      MIT
// @match        https://www.youtube.com/watch?*
// @icon         https://github.com/Sillylittleguy1/Youtube-embed-adblock/blob/main/Firefox/icon128.png?raw=true
// @grant        none
// @run-at       document-idle
// @author       Cave johnson
// @supportURL   https://github.com/Sillylittleguy1/Youtube-embed-adblock/issues/new
// ==/UserScript==

(function () {
    'use strict';
    console.log("[YT-Embed] Script Loaded");

    const config = {
        playerId: 'player',
        embedClass: 'custom-youtube-embed',
        defaultHeight: '390px',
        aspectRatio: 16 / 9,
        maxHeightOffset: 120,
        autoplay: true,
        bevelSize: '12px'
    };

    function createEmbedPlayer(videoId) {
        const container = document.createElement('div');
        container.className = config.embedClass;

        container.style.cssText = `
            min-height: ${config.defaultHeight};
            height: calc(100vw * ${1 / config.aspectRatio});
            max-height: calc(100vh - ${config.maxHeightOffset}px);
            width: 100%;
            position: relative;
            background-color: #000;
            border-radius: ${config.bevelSize};
            overflow: hidden;
            box-shadow: inset 0 0 ${config.bevelSize} rgba(255, 255, 255, 0.1);
        `;

        const embedUrl = `https://www.youtube.com/embed/${videoId}?${
            config.autoplay ? 'autoplay=1&' : ''
        }rel=0&modestbranding=1&iv_load_policy=3&disablekb=1`;

        const shellHtml = `
            <!DOCTYPE html>
            <html>
            <body style="margin:0;background:#000;">
                <iframe src="${embedUrl}" allowfullscreen allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" style="border:none;width:100vw;height:100vh;"></iframe>
            </body>
            </html>
        `;

        const blob = new Blob([shellHtml], { type: 'text/html' });
        const blobUrl = URL.createObjectURL(blob);

        const iframe = document.createElement('iframe');
        iframe.src = blobUrl;
        iframe.style.cssText = `
            width: 100%;
            height: 100%;
            border: none;
            display: block;
        `;
        iframe.allowFullscreen = true;

        container.appendChild(iframe);
        return container;
    }

    function stopNativeYouTubePlayer() {
        const moviePlayer = document.getElementById('movie_player');
        if (moviePlayer) {
            try {
                moviePlayer.pauseVideo?.();
                moviePlayer.stopVideo?.();
                moviePlayer.destroy?.();
                moviePlayer.innerHTML = ''; // blank out video tag if needed
            } catch (e) {
                console.warn('[YT-Embed] Failed to destroy movie_player:', e);
            }
        }

        // Also stop any <video> elements that got injected early
        document.querySelectorAll('video').forEach(video => {
            try {
                video.pause();
                video.src = '';
                video.load();
            } catch {}
        });
    }

    function replacePlayer() {
        const videoId = new URLSearchParams(window.location.search).get('v');
        if (!videoId) return;

        const player = document.getElementById(config.playerId);
        if (!player) return;

        if (player.firstChild && player.firstChild.classList?.contains(config.embedClass)) return;

        player.innerHTML = '';
        const embedPlayer = createEmbedPlayer(videoId);
        player.appendChild(embedPlayer);

        // 👇 Kill native player
        stopNativeYouTubePlayer();

        // 👇 Block future re-initialization
        blockYouTubePlayerAPI();
    }

    function muteMainPageAudio() {
        const AudioCtx = window.AudioContext || window.webkitAudioContext;
        if (!AudioCtx) return;

        const OriginalAudioContext = AudioCtx;
        function SilentAudioContext(...args) {
            const ctx = new OriginalAudioContext(...args);
            ctx.suspend?.().catch(() => {});
            return ctx;
        }

        SilentAudioContext.prototype = OriginalAudioContext.prototype;
        window.AudioContext = SilentAudioContext;
        window.webkitAudioContext = SilentAudioContext;

        AudioCtx.prototype.resume = () => Promise.resolve();
        AudioCtx.prototype.createGain = () => ({
            gain: { value: 0, setValueAtTime() {}, linearRampToValueAtTime() {} }
        });
    }

    function init() {
        muteMainPageAudio();
        replacePlayer();
        stopNativeYouTubePlayer();

        // Watch for YouTube re-initializing the player
        const observer = new MutationObserver(() => {
            replacePlayer();
        });
        observer.observe(document.body, { childList: true, subtree: true });

        // Also hook into navigation
        const originalPushState = history.pushState;
        history.pushState = function () {
            originalPushState.apply(this, arguments);
            setTimeout(replacePlayer, 500);
        };
        window.addEventListener('popstate', () => setTimeout(replacePlayer, 500));

        // As extra safety: retry for a few seconds after load
        let tries = 0;
        const interval = setInterval(() => {
            if (tries++ > 40) clearInterval(interval); // stop after ~12s
            replacePlayer();
        }, 300);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        setTimeout(init, 300);
    }
})();