Disable Video Autoplay

Prevents videos from autoplaying on websites and adds a play button overlay

// ==UserScript==
// @name         Disable Video Autoplay
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Prevents videos from autoplaying on websites and adds a play button overlay
// @author       You
// @match        *://*/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';
    
    // User-configurable options
    const config = {
        // List of domains where autoplay blocking is disabled (e.g. 'youtube.com')
        allowlist: [],
        // List of domains where autoplay blocking is always enabled (leave empty to target all sites)
        denylist: [],
        // Show a play button overlay on videos
        showPlayButton: true,
        // Enable debug logging in the console
        debug: false
    };

    // Decide if the current site should be processed based on allowlist/denylist
    function shouldProcessSite() {
        const currentDomain = window.location.hostname.replace('www.', '');
        
        // Skip processing if the site is in the allowlist
        if (config.allowlist.some(site => currentDomain.includes(site))) {
            logDebug('Site in allowlist, skipping');
            return false;
        }
        
        // Only process if denylist is empty or the site is in the denylist
        if (config.denylist.length > 0 && !config.denylist.some(site => currentDomain.includes(site))) {
            logDebug('Site not in denylist, skipping');
            return false;
        }
        
        return true;
    }

    // Print debug messages if debug mode is enabled
    function logDebug(message) {
        if (config.debug) {
            console.log('[Disable Autoplay]', message);
        }
    }

    // Remove autoplay from <video> elements and prevent programmatic autoplay
    function processVideoElement(video) {
        if (video.hasAttribute('autoplay')) {
            video.removeAttribute('autoplay');
            video.pause();
            logDebug('Removed autoplay attribute from video element');
        }
        
        // Unmute video (some sites use muted+autoplay to bypass restrictions)
        video.muted = false;
        
        // Prevent autoplay even if triggered by scripts
        video.addEventListener('play', function(e) {
            if (!video.hasAttribute('data-user-initiated')) {
                video.pause();
                logDebug('Prevented automatic playback');
            }
        }, true);
        
        // Add overlay if enabled and not already present
        if (config.showPlayButton && !video.hasAttribute('data-overlay-added')) {
            addPlayButtonOverlay(video);
        }
    }

    // Modify <iframe> embeds (e.g. YouTube, Vimeo) to disable autoplay
    function processIframeElement(iframe) {
        // Special handling for YouTube/Vimeo embeds
        if (iframe.src && iframe.src.match(/youtube|vimeo/i)) {
            const currentSrc = iframe.src;
            // If autoplay=1, set to 0
            if (currentSrc.includes('autoplay=1')) {
                iframe.src = currentSrc.replace('autoplay=1', 'autoplay=0');
                logDebug('Disabled autoplay for embedded video iframe');
            } else if (!currentSrc.includes('autoplay=0')) {
                // If no autoplay param, add autoplay=0
                iframe.src = currentSrc + (currentSrc.includes('?') ? '&' : '?') + 'autoplay=0';
                logDebug('Added autoplay=0 parameter to iframe');
            }
        }
        
        // For other iframes, try to replace autoplay=1 with autoplay=0 in the src
        else if (iframe.src && iframe.src.includes('autoplay')) {
            try {
                iframe.src = iframe.src.replace(/autoplay=1/g, 'autoplay=0');
                logDebug('Modified autoplay parameter in iframe src');
            } catch (e) {
                logDebug('Could not modify iframe src: ' + e.message);
            }
        }
    }

    // Add a play button overlay to the video element
    function addPlayButtonOverlay(video) {
        // Don't add overlay if already present
        if (video.hasAttribute('data-overlay-added')) {
            return;
        }
        
        // Mark as processed so overlay isn't added again
        video.setAttribute('data-overlay-added', 'true');
        
        // Create overlay container
        const container = document.createElement('div');
        container.style.cssText = `
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: flex;
            align-items: center;
            justify-content: center;
            background-color: rgba(0, 0, 0, 0.2);
            z-index: 10000;
            cursor: pointer;
            opacity: 0;
            transition: opacity 0.3s;
        `;
        container.classList.add('autoplay-disabled-overlay');
        
        // Create the circular play button
        const playButton = document.createElement('div');
        playButton.style.cssText = `
            width: 60px;
            height: 60px;
            background-color: rgba(0, 0, 0, 0.6);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
        `;
        
        // Create the triangle play icon
        const playIcon = document.createElement('div');
        playIcon.style.cssText = `
            width: 0;
            height: 0;
            border-top: 15px solid transparent;
            border-bottom: 15px solid transparent;
            border-left: 25px solid white;
            margin-left: 5px;
        `;
        
        // Assemble overlay
        playButton.appendChild(playIcon);
        container.appendChild(playButton);
        
        // Make sure the parent is positioned for absolute overlay
        const videoParent = video.parentElement;
        videoParent.style.position = videoParent.style.position || 'relative';
        videoParent.appendChild(container);
        
        // Show overlay on hover
        videoParent.addEventListener('mouseenter', () => {
            container.style.opacity = '1';
        });
        
        videoParent.addEventListener('mouseleave', () => {
            container.style.opacity = '0';
        });
        
        // Play video when overlay is clicked
        container.addEventListener('click', () => {
            video.setAttribute('data-user-initiated', 'true');
            video.play()
                .then(() => {
                    // Hide overlay when playback starts
                    container.style.display = 'none';
                    logDebug('Video playback started by user');
                })
                .catch(err => {
                    logDebug('Error starting video playback: ' + err.message);
                    // Keep overlay visible if playback fails
                });
        });
        
        logDebug('Added play button overlay to video');
    }

    // Scan and process all <video> and <iframe> elements currently in the DOM
    function processExistingMedia() {
        document.querySelectorAll('video').forEach(processVideoElement);
        document.querySelectorAll('iframe').forEach(processIframeElement);
        logDebug('Processed existing media elements');
    }

    // Watch for new videos/iframes added dynamically and process them
    function setupMutationObserver() {
        const observer = new MutationObserver((mutations) => {
            let needsProcessing = false;
            
            // If any new nodes are added, flag for processing
            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length) {
                    needsProcessing = true;
                }
            });
            
            // Batch process after a short delay
            if (needsProcessing) {
                setTimeout(() => {
                    processExistingMedia();
                }, 100); // Let DOM settle
            }
        });
        
        // Observe the whole document for added nodes
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
        
        logDebug('Mutation observer set up for dynamic content');
    }

    // Main entry point: set up everything
    function initialize() {
        if (!shouldProcessSite()) {
            return;
        }
        
        logDebug('Initializing autoplay blocker');
        
        // Add style for overlays
        const style = document.createElement('style');
        style.textContent = `
            video[data-overlay-added] {
                visibility: visible !important;
                opacity: 1 !important;
            }
        `;
        document.head.appendChild(style);
        
        // Process any existing media right away
        processExistingMedia();
        
        // Watch for new media
        setupMutationObserver();
        
        // Re-scan after short delays to catch late-loaded elements
        setTimeout(processExistingMedia, 1000);
        setTimeout(processExistingMedia, 3000);
    }

    // Run as soon as possible
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
})();