YouTube Video Controller

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})();
YouTube Video Controller | Robomonkey