Pause videos at startup, hide controls until hover, disable autoplay/captions, and add screenshot button
Size
8.3 KB
Version
1.0.1
Created
Oct 18, 2025
Updated
5 days ago
1// ==UserScript==
2// @name YouTube Video Controller
3// @description Pause videos at startup, 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 Controller: Extension loaded');
12
13 // Debounce function for MutationObserver
14 function debounce(func, wait) {
15 let timeout;
16 return function executedFunction(...args) {
17 const later = () => {
18 clearTimeout(timeout);
19 func(...args);
20 };
21 clearTimeout(timeout);
22 timeout = setTimeout(later, wait);
23 };
24 }
25
26 // Feature 1: Pause video at startup
27 function pauseVideoAtStartup() {
28 const video = document.querySelector('video[src]');
29 if (video && !video.paused) {
30 console.log('YouTube Video Controller: Pausing video at startup');
31 video.pause();
32 }
33 }
34
35 // Feature 2: Hide video controls, show only on status bar hover
36 function setupControlsHoverBehavior() {
37 const player = document.querySelector('.html5-video-player[class*="ytp-"]');
38 const controlsBar = document.querySelector('.ytp-chrome-bottom');
39
40 if (!player || !controlsBar) return;
41
42 // Add custom styles for hover behavior
43 const style = document.createElement('style');
44 style.textContent = `
45 .html5-video-player .ytp-chrome-bottom {
46 opacity: 0 !important;
47 transition: opacity 0.3s ease !important;
48 }
49 .html5-video-player .ytp-chrome-bottom:hover {
50 opacity: 1 !important;
51 }
52 .html5-video-player.ytp-autohide .ytp-chrome-bottom {
53 opacity: 0 !important;
54 }
55 .html5-video-player.ytp-autohide .ytp-chrome-bottom:hover {
56 opacity: 1 !important;
57 }
58 `;
59
60 if (!document.getElementById('yt-controls-hover-style')) {
61 style.id = 'yt-controls-hover-style';
62 document.head.appendChild(style);
63 console.log('YouTube Video Controller: Controls hover behavior applied');
64 }
65 }
66
67 // Feature 3: Disable autoplay next
68 function disableAutoplayNext() {
69 const autoplayToggle = document.querySelector('.ytp-autonav-toggle-button[aria-checked="true"]');
70 if (autoplayToggle) {
71 autoplayToggle.click();
72 console.log('YouTube Video Controller: Autoplay disabled');
73 }
74 }
75
76 // Feature 4: Disable subtitles/closed captions by default
77 function disableSubtitles() {
78 const video = document.querySelector('video[src]');
79 if (video && video.textTracks) {
80 for (let i = 0; i < video.textTracks.length; i++) {
81 if (video.textTracks[i].mode === 'showing') {
82 video.textTracks[i].mode = 'disabled';
83 console.log('YouTube Video Controller: Subtitles disabled');
84 }
85 }
86 }
87
88 // Also click the CC button if it's active
89 const ccButton = document.querySelector('.ytp-subtitles-button[aria-pressed="true"]');
90 if (ccButton) {
91 ccButton.click();
92 console.log('YouTube Video Controller: CC button toggled off');
93 }
94 }
95
96 // Feature 5: Add screenshot button
97 function addScreenshotButton() {
98 // Check if button already exists
99 if (document.getElementById('yt-screenshot-btn')) return;
100
101 const video = document.querySelector('video[src]');
102 const playerContainer = document.querySelector('.html5-video-player');
103
104 if (!video || !playerContainer) return;
105
106 // Create screenshot button
107 const screenshotBtn = document.createElement('button');
108 screenshotBtn.id = 'yt-screenshot-btn';
109 screenshotBtn.innerHTML = `
110 <svg width="20" height="20" viewBox="0 0 24 24" fill="white">
111 <path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
112 </svg>
113 `;
114 screenshotBtn.title = 'Take Screenshot';
115
116 // Style the button
117 screenshotBtn.style.cssText = `
118 position: absolute;
119 top: 10px;
120 right: 10px;
121 width: 36px;
122 height: 36px;
123 background-color: rgba(0, 0, 0, 0.7);
124 border: none;
125 border-radius: 4px;
126 cursor: pointer;
127 z-index: 9999;
128 display: flex;
129 align-items: center;
130 justify-content: center;
131 transition: background-color 0.2s ease;
132 `;
133
134 // Hover effect
135 screenshotBtn.addEventListener('mouseenter', () => {
136 screenshotBtn.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
137 });
138 screenshotBtn.addEventListener('mouseleave', () => {
139 screenshotBtn.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
140 });
141
142 // Screenshot functionality
143 screenshotBtn.addEventListener('click', () => {
144 console.log('YouTube Video Controller: Taking screenshot');
145
146 // Create canvas
147 const canvas = document.createElement('canvas');
148 canvas.width = video.videoWidth;
149 canvas.height = video.videoHeight;
150 const ctx = canvas.getContext('2d');
151
152 // Draw video frame to canvas
153 ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
154
155 // Convert to blob and download
156 canvas.toBlob((blob) => {
157 const url = URL.createObjectURL(blob);
158 const a = document.createElement('a');
159 a.href = url;
160
161 // Generate filename with video title and timestamp
162 const videoTitle = document.querySelector('h1.ytd-watch-metadata yt-formatted-string')?.textContent || 'youtube-video';
163 const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
164 a.download = `${videoTitle.slice(0, 50)}_${timestamp}.png`;
165
166 a.click();
167 URL.revokeObjectURL(url);
168
169 // Visual feedback
170 screenshotBtn.style.backgroundColor = 'rgba(0, 255, 0, 0.7)';
171 setTimeout(() => {
172 screenshotBtn.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
173 }, 300);
174
175 console.log('YouTube Video Controller: Screenshot saved');
176 }, 'image/png');
177 });
178
179 playerContainer.appendChild(screenshotBtn);
180 console.log('YouTube Video Controller: Screenshot button added');
181 }
182
183 // Initialize all features
184 function initializeFeatures() {
185 console.log('YouTube Video Controller: Initializing features');
186
187 // Wait a bit for YouTube to load
188 setTimeout(() => {
189 pauseVideoAtStartup();
190 setupControlsHoverBehavior();
191 disableAutoplayNext();
192 disableSubtitles();
193 addScreenshotButton();
194 }, 1000);
195 }
196
197 // Re-apply features on navigation (YouTube is a SPA)
198 const debouncedInit = debounce(() => {
199 console.log('YouTube Video Controller: Re-initializing after navigation');
200 pauseVideoAtStartup();
201 setupControlsHoverBehavior();
202 disableAutoplayNext();
203 disableSubtitles();
204 addScreenshotButton();
205 }, 500);
206
207 // Watch for navigation changes
208 const observer = new MutationObserver(debouncedInit);
209
210 // Start observing when body is ready
211 if (document.body) {
212 observer.observe(document.body, {
213 childList: true,
214 subtree: true
215 });
216 }
217
218 // Initial setup
219 if (document.readyState === 'loading') {
220 document.addEventListener('DOMContentLoaded', initializeFeatures);
221 } else {
222 initializeFeatures();
223 }
224
225 // Also listen for YouTube's navigation events
226 window.addEventListener('yt-navigate-finish', () => {
227 console.log('YouTube Video Controller: Navigation detected');
228 debouncedInit();
229 });
230
231 console.log('YouTube Video Controller: Setup complete');
232})();