Auto-pause on load, hide controls until hover, disable autoplay/captions, and add screenshot button
Size
8.8 KB
Version
1.0.1
Created
Oct 18, 2025
Updated
3 days ago
1// ==UserScript==
2// @name YouTube Video Control Suite
3// @description Auto-pause on load, hide controls until hover, disable autoplay/captions, and add screenshot button
4// @version 1.0.1
5// @match https://*.youtube.com/*
6// @icon https://www.youtube.com/s/desktop/a192c735/img/favicon_32x32.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('YouTube Video Control Suite initialized');
12
13 let videoPausedOnce = false;
14 let controlsHidden = false;
15 let screenshotButton = null;
16
17 // Debounce function for performance
18 function debounce(func, wait) {
19 let timeout;
20 return function executedFunction(...args) {
21 const later = () => {
22 clearTimeout(timeout);
23 func(...args);
24 };
25 clearTimeout(timeout);
26 timeout = setTimeout(later, wait);
27 };
28 }
29
30 // Feature 1: Pause video on page load (one time only)
31 function pauseVideoOnLoad(video) {
32 if (!videoPausedOnce && !video.paused) {
33 console.log('Pausing video on initial load');
34 video.pause();
35 videoPausedOnce = true;
36 }
37 }
38
39 // Feature 2: Hide video controls, show only on control bar hover
40 function setupControlsVisibility(video) {
41 if (controlsHidden) return;
42
43 const player = document.querySelector('.html5-video-player');
44 if (!player) return;
45
46 const controlsBar = document.querySelector('.ytp-chrome-bottom');
47 if (!controlsBar) return;
48
49 console.log('Setting up controls visibility');
50
51 // Add custom styles to hide controls by default
52 TM_addStyle(`
53 .html5-video-player .ytp-chrome-bottom {
54 opacity: 0 !important;
55 transition: opacity 0.3s ease !important;
56 }
57
58 .html5-video-player .ytp-chrome-bottom:hover {
59 opacity: 1 !important;
60 }
61
62 .html5-video-player.ytp-autohide .ytp-chrome-bottom:hover {
63 opacity: 1 !important;
64 }
65 `);
66
67 controlsHidden = true;
68 }
69
70 // Feature 3: Disable autoplay next
71 function disableAutoplayNext() {
72 const autoplayToggle = document.querySelector('.ytp-autonav-toggle-button');
73 if (autoplayToggle) {
74 const isAutoplayOn = autoplayToggle.getAttribute('aria-checked') === 'true';
75 if (isAutoplayOn) {
76 console.log('Disabling autoplay next');
77 autoplayToggle.click();
78 }
79 }
80 }
81
82 // Feature 4: Disable subtitles/closed captions by default
83 function disableSubtitles(video) {
84 const player = document.querySelector('.html5-video-player');
85 if (!player) return;
86
87 const subtitlesButton = document.querySelector('.ytp-subtitles-button');
88 if (subtitlesButton) {
89 const isSubtitlesOn = subtitlesButton.getAttribute('aria-pressed') === 'true';
90 if (isSubtitlesOn) {
91 console.log('Disabling subtitles');
92 subtitlesButton.click();
93 }
94 }
95
96 // Also disable text tracks directly
97 if (video.textTracks) {
98 for (let i = 0; i < video.textTracks.length; i++) {
99 if (video.textTracks[i].mode === 'showing') {
100 video.textTracks[i].mode = 'disabled';
101 }
102 }
103 }
104 }
105
106 // Feature 5: Add screenshot button
107 function createScreenshotButton(video) {
108 if (screenshotButton) return;
109
110 const player = document.querySelector('.html5-video-player');
111 if (!player) return;
112
113 console.log('Creating screenshot button');
114
115 // Create button
116 screenshotButton = document.createElement('button');
117 screenshotButton.id = 'yt-screenshot-btn';
118 screenshotButton.innerHTML = '📷';
119 screenshotButton.title = 'Take Screenshot';
120
121 // Style the button
122 screenshotButton.style.cssText = `
123 position: absolute;
124 top: 10px;
125 right: 10px;
126 width: 36px;
127 height: 36px;
128 background: rgba(0, 0, 0, 0.7);
129 border: 2px solid rgba(255, 255, 255, 0.3);
130 border-radius: 4px;
131 color: white;
132 font-size: 18px;
133 cursor: pointer;
134 z-index: 9999;
135 opacity: 0;
136 transition: opacity 0.3s ease, background 0.2s ease;
137 display: flex;
138 align-items: center;
139 justify-content: center;
140 padding: 0;
141 `;
142
143 // Show button on hover
144 player.addEventListener('mouseenter', () => {
145 screenshotButton.style.opacity = '1';
146 });
147
148 player.addEventListener('mouseleave', () => {
149 screenshotButton.style.opacity = '0';
150 });
151
152 // Hover effect on button
153 screenshotButton.addEventListener('mouseenter', () => {
154 screenshotButton.style.background = 'rgba(0, 0, 0, 0.9)';
155 screenshotButton.style.borderColor = 'rgba(255, 255, 255, 0.6)';
156 });
157
158 screenshotButton.addEventListener('mouseleave', () => {
159 screenshotButton.style.background = 'rgba(0, 0, 0, 0.7)';
160 screenshotButton.style.borderColor = 'rgba(255, 255, 255, 0.3)';
161 });
162
163 // Screenshot functionality
164 screenshotButton.addEventListener('click', () => {
165 console.log('Taking screenshot');
166
167 const canvas = document.createElement('canvas');
168 canvas.width = video.videoWidth;
169 canvas.height = video.videoHeight;
170
171 const ctx = canvas.getContext('2d');
172 ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
173
174 canvas.toBlob((blob) => {
175 const url = URL.createObjectURL(blob);
176 const a = document.createElement('a');
177 a.href = url;
178
179 // Generate filename with video title and timestamp
180 const videoTitle = document.querySelector('h1.ytd-watch-metadata yt-formatted-string')?.textContent || 'youtube-video';
181 const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
182 const currentTime = Math.floor(video.currentTime);
183
184 a.download = `${videoTitle.slice(0, 50)}_${currentTime}s_${timestamp}.png`;
185 a.click();
186
187 URL.revokeObjectURL(url);
188
189 // Visual feedback
190 screenshotButton.innerHTML = '✓';
191 setTimeout(() => {
192 screenshotButton.innerHTML = '📷';
193 }, 1000);
194 });
195 });
196
197 player.appendChild(screenshotButton);
198 }
199
200 // Main initialization function
201 function initializeFeatures() {
202 const video = document.querySelector('video.html5-main-video');
203 if (!video) return;
204
205 console.log('Video element found, initializing features');
206
207 // Feature 1: Pause on load
208 pauseVideoOnLoad(video);
209
210 // Feature 2: Setup controls visibility
211 setupControlsVisibility(video);
212
213 // Feature 3: Disable autoplay (check periodically as it might load later)
214 disableAutoplayNext();
215 setTimeout(disableAutoplayNext, 2000);
216 setTimeout(disableAutoplayNext, 5000);
217
218 // Feature 4: Disable subtitles
219 disableSubtitles(video);
220 setTimeout(() => disableSubtitles(video), 1000);
221
222 // Feature 5: Create screenshot button
223 createScreenshotButton(video);
224 }
225
226 // Watch for video element and player changes
227 const observer = new MutationObserver(debounce(() => {
228 initializeFeatures();
229 }, 500));
230
231 // Start observing when body is ready
232 function startObserving() {
233 if (document.body) {
234 observer.observe(document.body, {
235 childList: true,
236 subtree: true
237 });
238 initializeFeatures();
239 } else {
240 setTimeout(startObserving, 100);
241 }
242 }
243
244 // Handle YouTube's SPA navigation
245 let lastUrl = location.href;
246 new MutationObserver(() => {
247 const currentUrl = location.href;
248 if (currentUrl !== lastUrl) {
249 console.log('URL changed, reinitializing features');
250 lastUrl = currentUrl;
251 videoPausedOnce = false;
252 controlsHidden = false;
253 screenshotButton = null;
254 setTimeout(initializeFeatures, 1000);
255 }
256 }).observe(document.querySelector('title'), { childList: true, subtree: true });
257
258 // Initialize
259 if (document.readyState === 'loading') {
260 document.addEventListener('DOMContentLoaded', startObserving);
261 } else {
262 startObserving();
263 }
264
265 console.log('YouTube Video Control Suite loaded');
266})();