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