Disable autoplay

Block autoplay before user interaction on most websites, tracking each media element separately

当前为 2024-09-15 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Disable autoplay
// @namespace    https://www.androidacy.com/
// @version      2.6.0
// @description  Block autoplay before user interaction on most websites, tracking each media element separately
// @author       Androidacy
// @include      *
// @icon         https://www.androidacy.com/wp-content/uploads/cropped-cropped-cropped-cropped-New-Project-32-69C2A87-1-192x192.jpg
// @grant        none
// @run-at       document-end
// ==/UserScript==

(() => {
    'use strict';

    // WeakMap to track user interaction for each media element
    const mediaInteractionMap = new WeakMap();

    // Helper function to pause all media elements
    const pauseAllMedia = () => {
        document.querySelectorAll('video, audio').forEach(media => {
            media.pause();
        });
    };

    // Initial pause of any playing media
    pauseAllMedia();

    // Remove autoplay attributes from existing media elements
    const removeAutoplay = (media) => {
        media.removeAttribute('autoplay');
    };

    // Detect if a click/touch is on the media or its cover
    const isTrustedInteraction = (event, media) => {
        if (!event.isTrusted) return false;

        const mediaRect = media.getBoundingClientRect();
        const x = event.clientX;
        const y = event.clientY;

        // Check if the interaction is within the media bounds
        if (
            x >= mediaRect.left &&
            x <= mediaRect.right &&
            y >= mediaRect.top &&
            y <= mediaRect.bottom
        ) {
            return true;
        }

        // Check if within 64px of the sides
        const within64px =
            x >= mediaRect.left - 64 &&
            x <= mediaRect.right + 64 &&
            y >= mediaRect.top - 64 &&
            y <= mediaRect.bottom + 64;

        if (within64px) {
            return true;
        }

        // Additional check for overlapping elements (covers)
        const elementsAtPoint = document.elementsFromPoint(x, y);
        for (const el of elementsAtPoint) {
            if (el !== media && media.contains(el)) {
                return true;
            }
        }

        return false;
    };

    // Override play method to block autoplay
    const overridePlay = (media) => {
        const originalPlay = media.play.bind(media);
        media.play = async () => {
            if (mediaInteractionMap.get(media)) {
                try {
                    await originalPlay();
                } catch (e) {
                    console.error('Playback failed:', e);
                }
            } else {
                media.pause();
                console.log('Autoplay blocked for:', media);
                return Promise.reject(new Error('Autoplay is blocked.'));
            }
        };
    };

    // Handle user interactions to allow playback for specific media elements
    const handleUserInteraction = (event) => {
        const mediaElements = document.querySelectorAll('video, audio');
        mediaElements.forEach(media => {
            if (isTrustedInteraction(event, media)) {
                mediaInteractionMap.set(media, true);
                media.play().catch(() => {});
            }
        });
    };

    // Add event listeners for user interactions
    window.addEventListener('click', handleUserInteraction, true);
    window.addEventListener('touchstart', handleUserInteraction, true);

    // Process a media element: remove autoplay and override play
    const processMediaElement = (media) => {
        removeAutoplay(media);
        overridePlay(media);

        // Initialize interaction state as false
        if (!mediaInteractionMap.has(media)) {
            mediaInteractionMap.set(media, false);
        }

        // Listen for play events and pause if playback is not allowed
        media.addEventListener('play', () => {
            if (!mediaInteractionMap.get(media)) {
                media.pause();
                console.log('Playback paused for:', media, 'due to no user interaction.');
            }
        });
    };

    // Initial processing of existing media elements
    document.querySelectorAll('video, audio').forEach(processMediaElement);

    // Observe for dynamically added media elements
    const observer = new MutationObserver(mutations => {
        for (const mutation of mutations) {
            mutation.addedNodes.forEach(node => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    if (node.matches('video, audio')) {
                        processMediaElement(node);
                    }
                    // Also check within the subtree
                    node.querySelectorAll && node.querySelectorAll('video, audio').forEach(processMediaElement);
                }
            });
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

})();