您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The last userscript you'll ever need to disable autoplay videos on news sites and elsewhere
// ==UserScript== // @name Ultimate Autoplay Blocker // @namespace https://www.androidacy.com/ // @version 3.1.0 // @description The last userscript you'll ever need to disable autoplay videos on news sites and elsewhere // @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'; // WeakMap to track user interaction status for each media element const mediaInteractionMap = new WeakMap(); // Store original play method const originalPlay = HTMLMediaElement.prototype.play; // Track initialization status let initialized = false; // ID for interval timer let checkIntervalId = null; // Helper: Extract coordinates from event const getCoordinates = (event) => { if (!event) return null; try { if (event.type.startsWith('mouse')) { return { x: event.clientX, y: event.clientY }; } else if (event.type === 'touchstart' || event.type === 'touchmove' || event.type === 'touchend') { // Handle both active touches and changed touches (for touchend) const touch = event.touches?.[0] || event.changedTouches?.[0]; return touch ? { x: touch.clientX, y: touch.clientY } : null; } } catch (e) { // Silently fail if we can't extract coordinates } return null; }; // Helper: Check if interaction is related to media element const isMediaInteraction = (event, media) => { if (!event?.isTrusted || !media) return false; try { // For keyboard events on the media itself if ((event.type === 'keydown' || event.type === 'keyup') && (document.activeElement === media || media.contains(document.activeElement))) { return ['Space', ' ', 'Enter', 'k', 'K'].includes(event.key); } const coords = getCoordinates(event); if (!coords) return false; const { x, y } = coords; const rect = media.getBoundingClientRect(); // Direct interaction with the media element if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) { return true; } // Check if interaction is near the media (for controls that appear outside) const proximity = 64; // px if (x >= rect.left - proximity && x <= rect.right + proximity && y >= rect.top - proximity && y <= rect.bottom + proximity) { // Check elements at interaction position try { const elementsAtPoint = document.elementsFromPoint(x, y); if (!elementsAtPoint || elementsAtPoint.length === 0) return false; for (const el of elementsAtPoint) { // Check if element is or contains media controls if (el === media || media.contains(el) || el.contains(media)) { return true; } // Check for common control elements const tagName = el.tagName?.toLowerCase(); const role = el.getAttribute('role')?.toLowerCase(); const elClass = (el.className || '').toString().toLowerCase(); const elId = (el.id || '').toString().toLowerCase(); const ariaLabel = el.getAttribute('aria-label')?.toLowerCase(); // Check for play buttons and controls if (tagName === 'button' || role === 'button' || elClass.includes('play') || elId.includes('play') || elClass.includes('control') || elId.includes('control') || (ariaLabel && (ariaLabel.includes('play') || ariaLabel.includes('start'))) || el.onclick || el.parentElement?.onclick) { return true; } // Check for common player UI patterns const playerPatterns = ['player', 'video', 'media', 'youtube', 'vimeo', 'jwplayer']; for (const pattern of playerPatterns) { if (elClass.includes(pattern) || elId.includes(pattern)) { return true; } } } } catch (e) { // If elementsFromPoint fails, fall back to less precise detection return document.activeElement === media; } } } catch (e) { // If anything fails, be conservative return false; } return false; }; // Process a media element const processMedia = (media) => { if (!media || !(media instanceof HTMLMediaElement)) return; try { // Set initial interaction state if not already set if (!mediaInteractionMap.has(media)) { mediaInteractionMap.set(media, false); // Remove autoplay attribute if (media.hasAttribute('autoplay')) { media.removeAttribute('autoplay'); } // Disable autoplay property if (media.autoplay) { media.autoplay = false; } // Listen for play events to pause if autoplay attempted media.addEventListener('play', function(e) { if (!mediaInteractionMap.get(this)) { this.pause(); } }, true); // Ensure new sources don't trigger autoplay media.addEventListener('loadedmetadata', function() { if (!mediaInteractionMap.get(this)) { this.pause(); } }, true); // Pause if it's already playing if (!media.paused && !mediaInteractionMap.get(media)) { media.pause(); } } } catch (e) { // Ignore errors in processing } }; // Handle user interactions const handleUserInteraction = (event) => { if (!event?.isTrusted) return; try { document.querySelectorAll('video, audio').forEach(media => { if (isMediaInteraction(event, media)) { mediaInteractionMap.set(media, true); } }); } catch (e) { // Ignore errors in event handling } }; // Override HTMLMediaElement.prototype.play const overridePlayMethod = () => { try { HTMLMediaElement.prototype.play = function() { try { if (mediaInteractionMap.get(this)) { return originalPlay.apply(this); } else { this.pause(); return Promise.reject(new DOMException('NotAllowedError', 'Autoplay blocked by userscript')); } } catch (e) { // If there's an error in our override, fall back to original behavior return originalPlay.apply(this); } }; } catch (e) { // If we can't override play, continue with other protections } }; // Override setAttribute to block autoplay attribute const overrideSetAttribute = () => { try { const originalSetAttribute = Element.prototype.setAttribute; Element.prototype.setAttribute = function(name, value) { if (name === 'autoplay' && this instanceof HTMLMediaElement) { return; } return originalSetAttribute.call(this, name, value); }; } catch (e) { // If we can't override setAttribute, continue with other protections } }; // Process all media elements const processAllMedia = () => { try { document.querySelectorAll('video, audio').forEach(processMedia); } catch (e) { // Ignore errors when processing all media } }; // Set up mutation observer for dynamically added elements const setupObserver = () => { try { const observer = new MutationObserver(mutations => { let foundMedia = false; for (const mutation of mutations) { // Check for added nodes if (mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if (node instanceof HTMLMediaElement) { processMedia(node); foundMedia = true; } else if (node.querySelectorAll) { const mediaElements = node.querySelectorAll('video, audio'); if (mediaElements.length > 0) { foundMedia = true; mediaElements.forEach(processMedia); } } } } } // Check for attribute changes (autoplay) if (mutation.type === 'attributes' && mutation.attributeName === 'autoplay' && mutation.target instanceof HTMLMediaElement) { mutation.target.removeAttribute('autoplay'); } } // Only scan all media if we found some to avoid performance issues if (foundMedia) { processAllMedia(); } }); observer.observe(document.documentElement || document, { childList: true, subtree: true, attributes: true, attributeFilter: ['autoplay'] }); } catch (e) { // If observer setup fails, rely on interval checking instead ensureIntervalActive(); } }; // Add event listeners for user interactions const setupEventListeners = () => { try { // Desktop events ['click', 'mousedown'].forEach(eventType => { window.addEventListener(eventType, handleUserInteraction, true); }); // Mobile events ['touchstart', 'touchend'].forEach(eventType => { window.addEventListener(eventType, handleUserInteraction, { capture: true, passive: true }); }); // Keyboard events ['keydown'].forEach(eventType => { window.addEventListener(eventType, handleUserInteraction, true); }); } catch (e) { // If event setup fails, still continue with other protections } }; // Pause all currently playing media const pauseAllMedia = () => { try { document.querySelectorAll('video, audio').forEach(media => { if (!mediaInteractionMap.get(media) && !media.paused) { media.pause(); } }); } catch (e) { // Ignore errors when pausing } }; // Ensure the interval is active const ensureIntervalActive = () => { if (!checkIntervalId) { checkIntervalId = setInterval(() => { processAllMedia(); pauseAllMedia(); }, 2000); } }; // Initialize const initialize = () => { if (initialized) return; initialized = true; // Override methods to intercept JS autoplay overridePlayMethod(); overrideSetAttribute(); // Set up event listeners setupEventListeners(); // Process existing media elements processAllMedia(); // Pause any currently playing media pauseAllMedia(); // Set up mutation observer for dynamically added elements setupObserver(); // Ensure interval is active as a backup ensureIntervalActive(); }; // Initialize as early as possible if (document.readyState !== 'loading') { initialize(); } else { document.addEventListener('DOMContentLoaded', initialize, { once: true }); } // Backup initialization when window loads window.addEventListener('load', initialize, { once: true }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址