Block autoplay and require direct user interaction to play media, detecting covers via computed styles
当前为
// ==UserScript==
// @name Disable Autoplay
// @namespace https://www.androidacy.com/
// @version 1.7.1
// @description Block autoplay and require direct user interaction to play media, detecting covers via computed styles
// @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';
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 (allowedToPlay.has(media)) return;
debugLog('Processing media:', media);
// Remove autoplay attribute
if (media.hasAttribute('autoplay')) {
media.removeAttribute('autoplay');
debugLog('Removed autoplay attribute');
}
// Pause if playing
if (!media.paused) {
media.pause();
debugLog('Paused media');
}
// Override play method
const originalPlay = media.play;
media.play = function(...args) {
if (allowedToPlay.has(media)) {
debugLog('Playing media:', media);
return originalPlay.apply(media, args);
} else {
warnLog('Autoplay blocked:', media);
return Promise.reject(new Error('Autoplay is disabled by a userscript.'));
}
};
// Enable playback on trusted user interaction
const enablePlayback = (event) => {
if (!event.isTrusted) {
warnLog('Untrusted event ignored:', event);
return;
}
debugLog('User interaction detected:', event.type, 'on', event.target);
allowedToPlay.add(media);
media.play().catch(err => warnLog('Playback error:', err, media));
// Remove listeners after enabling
media.removeEventListener('click', enablePlayback);
media.removeEventListener('touchstart', enablePlayback);
removeCoverListeners(media, enablePlayback);
};
// Add event listeners to media
media.addEventListener('click', enablePlayback, { once: true });
media.addEventListener('touchstart', enablePlayback, { once: true });
debugLog('Added event listeners to media');
// Add event listeners to cover elements detected via computed styles
addCoverListeners(media, enablePlayback);
}
function addCoverListeners(media, handler) {
const covers = findCoverElements(media);
covers.forEach(cover => {
cover.addEventListener('click', handler, { once: true });
cover.addEventListener('touchstart', handler, { once: true });
debugLog('Added event listeners to cover:', cover);
});
}
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:', cover);
});
}
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;
if (display === 'none' || visibility === 'hidden' || pointerEvents === 'none') return;
if (position !== 'absolute' && position !== 'fixed' && position !== 'relative') return;
const siblingRect = sibling.getBoundingClientRect();
// Check if sibling overlaps significantly with media
const overlap = isOverlapping(mediaRect, siblingRect);
if (overlap) {
covers.push(sibling);
}
});
return covers;
}
function isOverlapping(rect1, rect2) {
const threshold = 0.3; // Minimum overlap percentage
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;
}
function processMediaElements() {
mediaTags.forEach(tag => {
document.querySelectorAll(tag).forEach(media => disableAutoplay(media));
});
}
function observeMedia() {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
mediaTags.forEach(tag => {
if (node.matches(tag)) {
debugLog('New media added:', node);
disableAutoplay(node);
}
node.querySelectorAll(tag).forEach(media => {
debugLog('New nested media added:', media);
disableAutoplay(media);
});
});
});
});
});
observer.observe(document.documentElement, { childList: true, subtree: true });
debugLog('Started observing DOM for new media elements');
}
function init() {
debugLog('Initializing DisableAutoplay userscript');
processMediaElements();
observeMedia();
}
// Run init at document-start
init();
// Also process at DOMContentLoaded to catch any late-loaded media
document.addEventListener('DOMContentLoaded', () => {
debugLog('DOMContentLoaded event fired');
processMediaElements();
});
})();