您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Block autoplay before user interaction on most websites
当前为
// ==UserScript== // @name Disable autoplay // @namespace https://www.androidacy.com/ // @version 2.0.0 // @description Block autoplay before user interaction on most websites // @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-start // ==/UserScript== (function() { 'use strict'; // Generate a random 4-8 character string for tracking processed elements const generateRandomString = () => { const length = Math.floor(Math.random() * 5) + 4; // 4-8 characters const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return `__disableAutoplay_${result}`; }; const processedProp = generateRandomString(); const allowedToPlay = new WeakSet(); const mediaTags = ['video', 'audio']; function debugLog(...args) { console.debug('[DisableAutoplay]', ...args); } function warnLog(...args) { console.warn('[DisableAutoplay]', ...args); } function disableAutoplay(media) { if (media[processedProp]) { debugLog('Already processed media element:', media); return; } media[processedProp] = true; debugLog('Processing media element:', media); // Remove autoplay attribute if present if (media.hasAttribute('autoplay')) { media.removeAttribute('autoplay'); debugLog('Removed autoplay attribute from media:', media); } // Pause the media if it's already playing if (!media.paused) { media.pause(); debugLog('Paused media element:', media); } // Store the original play method const originalPlay = media.play; // Override the play method to control playback media.play = function(...args) { if (allowedToPlay.has(media)) { debugLog('Playing media element:', media); return originalPlay.apply(media, args); } else { warnLog('Autoplay blocked for media element:', media); return Promise.reject(new Error('Autoplay is disabled by a userscript.')); } }; /** * Enables playback when a trusted user interaction is detected * @param {Event} event - The user interaction event */ const enablePlayback = (event) => { if (!event.isTrusted) { warnLog('Ignored untrusted event:', event); return; } debugLog('User interaction detected:', event.type, 'on', event.target); allowedToPlay.add(media); media.play().catch(err => warnLog('Error playing media after user interaction:', err, media)); // Remove event listeners after enabling playback media.removeEventListener('click', enablePlayback); media.removeEventListener('touchstart', enablePlayback); removeCoverListeners(media, enablePlayback); }; // Add event listeners to media element without passive listeners media.addEventListener('click', enablePlayback, { once: true, passive: false }); media.addEventListener('touchstart', enablePlayback, { once: true, passive: false }); debugLog('Added click and touchstart event listeners to media element:', media); // Add event listeners to associated cover elements addCoverListeners(media, enablePlayback); } /** * Adds event listeners to cover elements associated with the media * @param {HTMLMediaElement} media - The media element * @param {Function} handler - The event handler to attach */ function addCoverListeners(media, handler) { const covers = findCoverElements(media); covers.forEach(cover => { cover.addEventListener('click', handler, { once: true, passive: false }); cover.addEventListener('touchstart', handler, { once: true, passive: false }); debugLog('Added event listeners to cover element:', cover); }); } /** * Removes event listeners from cover elements after playback is enabled * @param {HTMLMediaElement} media - The media element * @param {Function} handler - The event handler to remove */ function removeCoverListeners(media, handler) { const covers = findCoverElements(media); covers.forEach(cover => { cover.removeEventListener('click', handler); cover.removeEventListener('touchstart', handler); debugLog('Removed event listeners from cover element:', cover); }); } /** * Finds cover elements associated with a media element by analyzing computed styles * @param {HTMLMediaElement} media - The media element * @returns {HTMLElement[]} - Array of cover elements */ function findCoverElements(media) { const covers = []; const mediaRect = media.getBoundingClientRect(); const parent = media.parentElement; if (!parent) return covers; // Iterate through parent's children to find overlapping elements Array.from(parent.children).forEach(sibling => { if (sibling === media) return; const style = window.getComputedStyle(sibling); const position = style.position; const display = style.display; const visibility = style.visibility; const pointerEvents = style.pointerEvents; // Skip elements that are not visible or interactable if (display === 'none' || visibility === 'hidden' || pointerEvents === 'none') return; // Consider elements with certain positioning if (!['absolute', 'fixed', 'relative'].includes(position)) return; const siblingRect = sibling.getBoundingClientRect(); // Check if sibling overlaps significantly with media if (isOverlapping(mediaRect, siblingRect)) { covers.push(sibling); } }); return covers; } /** * Determines if two rectangles overlap by a certain threshold * @param {DOMRect} rect1 - First rectangle * @param {DOMRect} rect2 - Second rectangle * @returns {boolean} - True if overlapping sufficiently, else false */ function isOverlapping(rect1, rect2) { const threshold = 0.3; // 30% overlap const intersection = { left: Math.max(rect1.left, rect2.left), right: Math.min(rect1.right, rect2.right), top: Math.max(rect1.top, rect2.top), bottom: Math.min(rect1.bottom, rect2.bottom) }; const width = intersection.right - intersection.left; const height = intersection.bottom - intersection.top; if (width <= 0 || height <= 0) return false; const areaIntersection = width * height; const areaMedia = rect1.width * rect1.height; return (areaIntersection / areaMedia) >= threshold; } /** * Processes all existing media elements on the page */ function processMediaElements() { mediaTags.forEach(tag => { document.querySelectorAll(tag).forEach(media => { disableAutoplay(media); }); }); } /** * Observes the DOM for any new media elements and processes them */ function observeMedia() { const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType !== Node.ELEMENT_NODE) return; mediaTags.forEach(tag => { // Direct match if (node.matches(tag)) { debugLog('New media element added:', node); disableAutoplay(node); } // Nested media elements node.querySelectorAll(tag).forEach(media => { debugLog('New nested media element added:', media); disableAutoplay(media); }); }); }); }); }); observer.observe(document.documentElement, { childList: true, subtree: true }); debugLog('Started observing DOM for new media elements'); } /** * Initializes the userscript by processing existing media elements and setting up observers */ function init() { debugLog('Initializing Disable autoplay userscript'); processMediaElements(); observeMedia(); } // Run initialization at document-start init(); // Also process media elements at DOMContentLoaded to catch any late-loaded media document.addEventListener('DOMContentLoaded', () => { debugLog('DOMContentLoaded event fired'); processMediaElements(); }); })();