Last active
January 1, 2026 09:49
-
-
Save tkhorik/0042837583ef675b3d217a317f78c220 to your computer and use it in GitHub Desktop.
Tampermonkey script preventing autoplay in YouTube on Chrome
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name YouTube — disable autoplay until I press Play | |
| // @namespace local.dmitry.youtube.autoplay.block | |
| // @version 2.0.0 | |
| // @description Prevent YouTube videos from auto-playing on open; only play after a recent user gesture. | |
| // @author you | |
| // @match https://www.youtube.com/* | |
| // @run-at document-start | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| // Allow play only if there was a user gesture in the last N ms | |
| const GESTURE_WINDOW_MS = 600; | |
| let lastGestureTs = 0; | |
| // Count clicks/taps/keyboard as gestures | |
| const markGesture = () => { lastGestureTs = Date.now(); }; | |
| window.addEventListener('pointerdown', markGesture, true); | |
| window.addEventListener('keydown', markGesture, true); | |
| // Helper: pause if the play wasn't user-initiated | |
| function guardPlay(ev) { | |
| const v = ev.target; | |
| if (!(v instanceof HTMLVideoElement)) return; | |
| const recentGesture = (Date.now() - lastGestureTs) <= GESTURE_WINDOW_MS; | |
| // If no recent gesture, immediately pause (blocks autoplay) | |
| if (!recentGesture) { | |
| // Prevent “play” from continuing | |
| try { v.pause(); } catch {} | |
| // Mark as not playing | |
| v.__blockedOnce = true; | |
| } | |
| } | |
| // On first attachment and on SPA navigations, ensure listeners exist and pause if already playing | |
| function attachToAllVideos() { | |
| document.querySelectorAll('video').forEach(v => { | |
| if (v.__autoplayGuardAttached) return; | |
| v.__autoplayGuardAttached = true; | |
| // If YouTube already started it, stop it right away | |
| try { | |
| if (!v.paused) v.pause(); | |
| } catch {} | |
| // Listen in capture so we run before player handlers | |
| v.addEventListener('play', guardPlay, true); | |
| }); | |
| } | |
| // Observe DOM for new video elements (YouTube swaps them frequently) | |
| const mo = new MutationObserver(() => attachToAllVideos()); | |
| mo.observe(document.documentElement, { childList: true, subtree: true }); | |
| // Handle YouTube SPA events (navigations without full reload) | |
| window.addEventListener('yt-navigate-finish', () => { | |
| // Reset gesture; a new page shouldn’t auto-play | |
| lastGestureTs = 0; | |
| attachToAllVideos(); | |
| // If a video element already exists, pause it once more | |
| const v = document.querySelector('video'); | |
| if (v) { try { v.pause(); } catch {} } | |
| }); | |
| // First pass (for hard reloads or when run-at is late) | |
| attachToAllVideos(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment