[Ad Block] A Manual Route

Discussion in 'Application Software' started by LTSC-Ten, Sep 29, 2026.

  1. LTSC-Ten

    LTSC-Ten MDL Member

    Sep 27, 2026
    175
    236
    10
    Several of my video streaming web sites cannot have their ads blocked by things like uBlock Origin. :mad:
    I'm not talking sites like YouTube, they can try all they want, uBO still finds a way! :cool:

    Pluto TV is an example. uBO cannot do anything about their ads.
    And they also serve the same EXACT ads time after time after time.
    Needless to say, the repetition makes those ads EXTREMELY annoying.

    Here is how I block them, an "out of sight, out of mind" approach.

    The video plays normally:
    upload_2026-9-29_6-35-47.png

    But when I hover the mouse over the upper right corner, several buttons appear:
    upload_2026-9-29_6-36-59.png

    30 seconds up to 5 minutes in 30sec intervals.
    Click a button and the underlying crap-ad is "hidden" (next pic).
    It mutes if originally unmuted, then returns to original unmuted after the countdown.
    It keeps unmuted if originally unmuted, then returns to original unmuted after countdown.
    The countdown can be clicked to cancel at any time, returning to original mute/unmute.
    The borders are blurred to view a "non-offensive" portion of screen to cancel if wanted.
    upload_2026-9-29_6-43-53.png


    Code:
    // ==UserScript==
    // @name - Add - Video - List - Blackout Overlay Button
    // @version 3.1.2
    // @match *://pluto.tv/*
    // @grant none
    // ==/UserScript==
    
    (function () {
        'use strict';
    
        /*** CONFIGURATION ***/
        const HOVER_WIDTH = 210;  // px from right edge
        const HOVER_HEIGHT = 66; // px from top edge
        const COLOR_CHANGE_INTERVAL = 30000; // 30 seconds in ms
        const CANCEL_COLORS = [
            '#3A3A3A', // Dark Charcoal
            '#4B3B5E', // Slate Violet
            '#004D7A', // Deep Azure
            '#007A7A', // Teal Blue
            '#007A33', // Emerald Green
            '#4F7A00', // Olive Green
            '#7A6A00', // Golden Olive
            '#947400', // Antique Gold
            '#A67F00', // Amber Gold
            '#B38B00'  // Rich Mustard Gold
        ];
    
        let originalMuteStates = [];
        let originalMuteStatusText = 'Unknown';
        let countdownInterval = null;
        let hideTimeout = null;
        let colorInterval = null;
        let currentColorIndex = 0;
        let hoverTimer = null;
    
        // Create container for buttons (5x2 grid)
        const buttonContainer = document.createElement('div');
        Object.assign(buttonContainer.style, {
            display: 'grid',
            gap: '5px',
            gridTemplateColumns: 'repeat(5, auto)',
            opacity: '0',
            pointerEvents: 'none',
            position: 'fixed',
                right: '5px',
                top: '5px',
            zIndex: '9999999'
        });
    
        // Create overlay trigger buttons
        const btn30Sec = createTriggerButton('0:30', '#2B0000');     // Deep Maroon
        const btn1Min = createTriggerButton('1:00', '#5E1A00');      // Rust Brown
        const btn1Min30Sec = createTriggerButton('1:30', '#7A4A00'); // Amber Ochre
        const btn2Min = createTriggerButton('2:00', '#6B7A00');      // Moss Green
        const btn2Min30Sec = createTriggerButton('2:30', '#1F7A00'); // Forest Green
        const btn3Min = createTriggerButton('3:00', '#007A3D');      // Sea Green
        const btn3Min30Sec = createTriggerButton('3:30', '#007A7A'); // Teal Blue
        const btn4Min = createTriggerButton('4:00', '#004D7A');      // Cerulean Blue
        const btn4Min30Sec = createTriggerButton('4:30', '#2B007A'); // Indigo Violet
        const btn5Min = createTriggerButton('5:00', '#7A00AA');      // Royal Purple
    
        buttonContainer.appendChild(btn30Sec);
        buttonContainer.appendChild(btn1Min);
        buttonContainer.appendChild(btn1Min30Sec);
        buttonContainer.appendChild(btn2Min);
        buttonContainer.appendChild(btn2Min30Sec);
        buttonContainer.appendChild(btn3Min);
        buttonContainer.appendChild(btn3Min30Sec);
        buttonContainer.appendChild(btn4Min);
        buttonContainer.appendChild(btn4Min30Sec);
        buttonContainer.appendChild(btn5Min);
    
        // Create first overlay (foreground layer)
        const overlay = document.createElement('div');
        overlay.id = 'blackoutOverlay';
        Object.assign(overlay.style, {
            alignItems: 'center',
            backgroundColor: '#000',
            display: 'none',
            displayFlex: 'flex',
            height: '100%',
            justifyContent: 'center',
            position: 'fixed',
                left: '0',
                top: '0',
            width: '100%',
            zIndex: '9999997',
            maskImage: `
                linear-gradient(to right, transparent 0%, black 40px, black calc(100% - 40px), transparent 100%),
                linear-gradient(to bottom, transparent 0%, black 30px, black calc(100% - 50px), transparent 100%)
            `,
            maskComposite: 'intersect',
            maskRepeat: 'no-repeat',
            maskPosition: 'center',
            maskSize: '100% 100%'
        });
    
        // Create second overlay (background layer)
        const overlay2 = document.createElement('div');
        overlay2.id = 'blackoutOverlay2';
        Object.assign(overlay2.style, {
            alignItems: 'center',
            backgroundColor: 'rgba(0, 0, 0, 0.6)',
            display: 'none',
            displayFlex: 'flex',
            height: '100%',
            justifyContent: 'center',
            position: 'fixed',
                left: '0',
                top: '0',
            width: '100%',
            zIndex: '9999995'
        });
    
        // Create cancel button with countdown + mute status
        const cancelBtn = document.createElement('button');
        Object.assign(cancelBtn.style, {
            backgroundColor: CANCEL_COLORS[0],
            border: 'none',
            borderRadius: '5px',
            color: '#fff',
            cursor: 'pointer',
            fontFamily: 'monospace',
            fontSize: '16px',
            lineHeight: '1.5',
            padding: '5px',
            textAlign: 'center',
            width: '80px'
        });
    
        overlay.appendChild(cancelBtn);
    
        /*** FUNCTIONS ***/
        function createTriggerButton(text, bgColor) {
            const btn = document.createElement('button');
            btn.id = 'blackoutButton' + text;
            btn.textContent = text;
            Object.assign(btn.style, {
                backgroundColor: bgColor,
                border: 'none',
                borderRadius: '5px',
                color: '#fff',
                cursor: 'pointer',
                height: '28px',
                fontFamily: 'monospace',
                fontSize: '14px',
                padding: '5px',
                textAlign: 'center',
                width: '40px'
            });
            return btn;
        }
    
        function muteMediaConditionally() {
            originalMuteStates = [];
            let anyUnmuted = false;
            document.querySelectorAll('video, audio').forEach(media => {
                originalMuteStates.push({ element: media, wasMuted: media.muted });
                if (!media.muted) {
                    anyUnmuted = true;
                    media.muted = true;
                }
            });
            originalMuteStatusText = anyUnmuted ? 'Unmuted' : 'Muted';
        }
    
        function restoreMuteStates() {
            originalMuteStates.forEach(({ element, wasMuted }) => {
                if (!wasMuted) {
                    element.muted = false;
                }
            });
            originalMuteStates = [];
        }
    
        function startColorCycle() {
            currentColorIndex = 0;
            cancelBtn.style.backgroundColor = CANCEL_COLORS[currentColorIndex];
            cancelBtn.id = 'blackoutCancel' + CANCEL_COLORS[currentColorIndex];
            colorInterval = setInterval(() => {
                currentColorIndex = (currentColorIndex + 1) % CANCEL_COLORS.length;
                cancelBtn.style.backgroundColor = CANCEL_COLORS[currentColorIndex];
                cancelBtn.id = 'blackoutCancel' + CANCEL_COLORS[currentColorIndex];
            }, COLOR_CHANGE_INTERVAL);
        }
    
        function stopColorCycle() {
            clearInterval(colorInterval);
            colorInterval = null;
        }
    
        function showOverlay(durationSeconds) {
            overlay.style.display = 'flex';
            overlay2.style.display = 'flex';
            muteMediaConditionally();
    
            const totalSegments = Math.ceil(durationSeconds / 30);
            let currentSegment = 1;
            let segmentTimeLeft = Math.min(30, durationSeconds); // start at 30 or less if short overlay
    
            updateCancelText(segmentTimeLeft, currentSegment, totalSegments);
            startColorCycle();
    
            countdownInterval = setInterval(() => {
                segmentTimeLeft--;
    
                if (segmentTimeLeft <= 0) {
                    // Move to next segment if time remains
                    if (currentSegment < totalSegments) {
                        currentSegment++;
                        segmentTimeLeft = Math.min(30, durationSeconds - (currentSegment - 1) * 30);
                    } else {
                        hideOverlay();
                        return;
                    }
                }
    
                updateCancelText(segmentTimeLeft, currentSegment, totalSegments);
            }, 1000);
    
            hideTimeout = setTimeout(hideOverlay, durationSeconds * 1000);
        }
    
        function updateCancelText(segmentSeconds, currentSegment, totalSegments) {
            const formattedSeconds = String(segmentSeconds).padStart(2, '0');
            cancelBtn.innerHTML =
                `${formattedSeconds}` +
                `<br>${currentSegment} of ${totalSegments}` +
                `<br>${originalMuteStatusText}`;
        }
    
        function hideOverlay() {
            overlay.style.display = 'none';
            overlay2.style.display = 'none';
            clearInterval(countdownInterval);
            clearTimeout(hideTimeout);
            stopColorCycle();
            restoreMuteStates();
        }
    
        /*** EVENT LISTENERS ***/
        btn5Min.addEventListener('click', () => showOverlay(300));
        btn4Min30Sec.addEventListener('click', () => showOverlay(270));
        btn4Min.addEventListener('click', () => showOverlay(240));
        btn3Min30Sec.addEventListener('click', () => showOverlay(210));
        btn3Min.addEventListener('click', () => showOverlay(180));
        btn2Min30Sec.addEventListener('click', () => showOverlay(150));
        btn2Min.addEventListener('click', () => showOverlay(120));
        btn1Min30Sec.addEventListener('click', () => showOverlay(90));
        btn1Min.addEventListener('click', () => showOverlay(60));
        btn30Sec.addEventListener('click', () => showOverlay(30));
        cancelBtn.addEventListener('click', hideOverlay);
    
        // Larger rectangular hover detection
        document.addEventListener('mousemove', (e) => {
            const inHoverZone =
                e.clientX >= window.innerWidth - HOVER_WIDTH &&
                e.clientY <= HOVER_HEIGHT;
            if (inHoverZone) {
                buttonContainer.style.opacity = '1';
                    // Start 5-second timer to hide
                    hoverTimer = setTimeout(() => {
                        buttonContainer.style.opacity = '0';
                    }, 5000);
                buttonContainer.style.pointerEvents = 'auto';
            } else {
                buttonContainer.style.opacity = '0';
                clearTimeout(hoverTimer);
                buttonContainer.style.pointerEvents = 'none';
            }
        });
    
        // Append elements to DOM
        document.body.appendChild(buttonContainer);
        document.body.appendChild(overlay);
        document.body.appendChild(overlay2);
    })();
     
  2. LTSC-Ten

    LTSC-Ten MDL Member

    Sep 27, 2026
    175
    236
    10
    <side note>
    I suppose the "scripting" section is a better place for this.
    Perhaps a Mod can move?
    </side note>
     
  3. Elliot Mercer

    Elliot Mercer MDL Novice

    Oct 16, 2026
    5
    1
    0
    This is actually a pretty practical approach for streaming sites where the same ads keep interrupting the viewing experience. I like that the script doesn’t try to interfere with the video itself; it simply gives the user a temporary way to hide the annoying part while keeping playback running.

    Repeated ads can be especially distracting when you’re watching movies on MovieBox or other long-form video content. I usually prefer a clean viewing experience where the video, audio, subtitles, and controls stay uninterrupted. The countdown option is also a nice touch because it gives the user control instead of permanently changing the page.

    This kind of small quality-of-life improvement can make a noticeable difference when watching movies through different streaming or movie-app setups.