YouTube Silent Part Skipper

A new userscript

Size

6.3 KB

Version

1.1.3

Created

Nov 27, 2025

Updated

19 days ago

1// ==UserScript==
2// @name		YouTube Silent Part Skipper
3// @description		A new userscript
4// @version		1.1.3
5// @match		https://*.youtube.com/*
6// @icon		https://www.youtube.com/s/desktop/271635d3/img/logos/favicon_32x32.png
7// ==/UserScript==
8(function() {
9    'use strict';
10    
11    let isEnabled = true;
12    let silenceThreshold = 0.01;
13    let silenceDuration = 2.0;
14    let skipSpeed = 8;
15    let audioContext = null;
16    let analyser = null;
17    let source = null;
18    let isAnalyzing = false;
19    let silenceStartTime = null;
20    let currentVideo = null;
21    let skipButton = null;
22    
23    async function loadSettings() {
24        isEnabled = await GM.getValue('silentSkipperEnabled', true);
25        silenceThreshold = await GM.getValue('silenceThreshold', 0.01);
26        silenceDuration = await GM.getValue('silenceDuration', 2.0);
27        skipSpeed = await GM.getValue('skipSpeed', 8);
28    }
29    
30    async function saveSettings() {
31        await GM.setValue('silentSkipperEnabled', isEnabled);
32        await GM.setValue('silenceThreshold', silenceThreshold);
33        await GM.setValue('silenceDuration', silenceDuration);
34        await GM.setValue('skipSpeed', skipSpeed);
35    }
36    
37    function createSkipButton() {
38        const controlsContainer = document.querySelector('.ytp-left-controls');
39        if (!controlsContainer) return;
40
41        // Remove any existing button with the data attribute
42        const existingButton = controlsContainer.querySelector('[data-silent-skipper]');
43        if (existingButton) {
44            existingButton.remove();
45        }
46
47        const button = document.createElement('button');
48        button.innerHTML = 'Skip Silent';
49        button.setAttribute('data-silent-skipper', 'true');
50        button.style.cssText = 'background: #ff0000; color: white; border: none; padding: 6px 12px; margin: 0 8px; border-radius: 2px; font-size: 12px; cursor: pointer; height: 36px; display: inline-flex; align-items: center; font-family: Roboto, Arial, sans-serif; font-weight: 500;';
51        button.title = 'Toggle silent part skipping (currently ' + (isEnabled ? 'ON' : 'OFF') + ')';
52
53        button.addEventListener('click', (e) => {
54            e.preventDefault();
55            e.stopPropagation();
56            isEnabled = !isEnabled;
57            saveSettings().then(() => updateButtonAppearance());
58        });
59
60        controlsContainer.appendChild(button);
61        skipButton = button;
62    }
63    
64    function updateButtonAppearance() {
65        if (skipButton) {
66            skipButton.style.backgroundColor = isEnabled ? '#ff0000' : '#666666';
67            skipButton.title = 'Toggle silent part skipping (currently ' + (isEnabled ? 'ON' : 'OFF') + ')';
68            skipButton.innerHTML = isEnabled ? 'Skip Silent' : 'Skip Silent (OFF)';
69        }
70    }
71    
72    function setupAudioContext(video) {
73        if (audioContext) {
74            audioContext.close();
75            audioContext = null;
76        }
77    
78        try {
79            audioContext = new AudioContext();
80            analyser = audioContext.createAnalyser();
81            analyser.fftSize = 2048;
82            source = audioContext.createMediaElementSource(video);
83            source.connect(analyser);
84            analyser.connect(audioContext.destination);
85            return true;
86        } catch (error) {
87            console.error('Audio context setup failed:', error);
88            audioContext = null;
89            analyser = null;
90            source = null;
91            return false;
92        }
93    }
94    
95    function detectSilence() {
96        if (!isAnalyzing || !analyser || !isEnabled || !currentVideo) return;
97
98        const dataArray = new Uint8Array(analyser.frequencyBinCount);
99        analyser.getByteFrequencyData(dataArray);
100
101        const average = dataArray.reduce((sum, value) => sum + value, 0) / dataArray.length / 255;
102
103        if (average < silenceThreshold) {
104            if (silenceStartTime === null) {
105                silenceStartTime = currentVideo.currentTime;
106            }
107    
108            if (silenceStartTime !== null && (currentVideo.currentTime - silenceStartTime) > silenceDuration) {
109                currentVideo.playbackRate = skipSpeed;
110                console.log('Skipping silent part at', currentVideo.currentTime);
111            }
112        } else {
113            if (silenceStartTime !== null) {
114                currentVideo.playbackRate = 1;
115                silenceStartTime = null;
116                console.log('Resumed normal speed at', currentVideo.currentTime);
117            }
118        }
119
120        requestAnimationFrame(detectSilence);
121    }
122    
123    function initializeVideoAnalysis(video) {
124        currentVideo = video;
125        const success = setupAudioContext(video);
126        if (success) {
127            isAnalyzing = true;
128            detectSilence();
129            console.log('Silent part detection initialized for video');
130        } else {
131            console.error('Failed to initialize audio analysis');
132        }
133    }
134    
135    function stopAnalysis() {
136        isAnalyzing = false;
137        if (audioContext) {
138            audioContext.close();
139            audioContext = null;
140        }
141        source = null;
142        analyser = null;
143        currentVideo = null;
144        silenceStartTime = null;
145    }
146    
147    function findAndSetupVideo() {
148        const video = document.querySelector('video.video-stream.html5-main-video');
149
150        if (!video || video === currentVideo) {
151            return video || null;
152        }
153
154        stopAnalysis();
155
156        video.addEventListener('loadeddata', () => {
157            initializeVideoAnalysis(video);
158        });
159
160        if (video.readyState >= 2) {
161            initializeVideoAnalysis(video);
162        }
163
164        return video;
165    }
166    
167    function observeVideoChanges() {
168        const observer = new MutationObserver(() => {
169            setTimeout(() => {
170                findAndSetupVideo();
171                createSkipButton();
172            }, 1000);
173        });
174
175        observer.observe(document.body, { childList: true, subtree: true });
176
177        return observer;
178    }
179    
180    async function init() {
181        await loadSettings();
182        findAndSetupVideo();
183        createSkipButton();
184        observeVideoChanges();
185        console.log('YouTube Silent Part Skipper initialized');
186    }
187    
188    if (document.readyState === 'loading') {
189        document.addEventListener('DOMContentLoaded', init);
190    } else {
191        init();
192    }
193})();
YouTube Silent Part Skipper | Robomonkey