Modern Speed Controller - Youtube/Music

—Hexation—

—Hexation—

Member
Joined
September 28, 2025
Messages
6
Reaction score
0
Points
1
JavaScript:
// ==UserScript==
// @name         YouTube Minimalistic Speed Controller
// @namespace    http://tampermonkey.net/
// @version      2.2.2
// @description  A self-contained, minimalistic speed controller that collapses after 5s of inactivity. Bounces on expand/collapse. Double-click speed to reset.
// @author       Gemini
// @match        *://*.youtube.com/*
// @exclude      *://music.youtube.com/*
// @exclude      *://studio.youtube.com/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration ---
    let userPreferredContentSpeed = 1.0;
    const MIN_CONTENT_SPEED = 0.1;
    const MAX_CONTENT_SPEED = 15.0;
    const SPEED_STEP = 0.1;
    const RESET_SPEED = 1.0;
    const INACTIVITY_TIMEOUT = 5000;

    // --- Global Variables ---
    let speedControllerElement, speedDisplayElement;
    let speedEnforceIntervalId, collapseTimer;
    let isCollapsed = false;

    // --- CSS Injection for Animations ---
    function injectCss() {
        document.getElementById('yt-speed-controller-styles')?.remove();
        const css = `
            #yt-speed-controller {
                height: 36px; /* MODIFIED: Slightly taller original height */
                width: 145px;
                padding: 5px 15px;
                transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1),
                            padding 0.6s cubic-bezier(0.34, 1.56, 0.64, 1),
                            height 0.6s cubic-bezier(0.34, 1.56, 0.64, 1),
                            opacity 0.3s ease;
            }
            #yt-speed-controller.collapsed {
                height: 27px; /* MODIFIED: 75% of original height */
                width: 72px;  /* MODIFIED: 50% of original width */
                align-items: flex-end; /* NEW: Aligns content to the bottom when collapsed */
            }
            #yt-speed-controller .yt-controller-inner-wrapper {
                opacity: 1;
                transition: opacity 0.2s 0.1s ease-in-out;
            }
            #yt-speed-controller.collapsed .yt-controller-inner-wrapper {
                opacity: 0;
                transition: opacity 0.1s ease-in-out;
            }
            #yt-speed-controller .yt-collapsed-indicator {
                opacity: 0;
                padding-bottom: 2px; /* Adjust vertical position of the dash */
                transition: opacity 0.1s ease-in-out;
            }
            #yt-speed-controller.collapsed .yt-collapsed-indicator {
                opacity: 1;
                transition: opacity 0.2s 0.1s ease-in-out;
            }
        `;
        const styleElement = document.createElement('style');
        styleElement.id = 'yt-speed-controller-styles';
        styleElement.textContent = css;
        document.head.appendChild(styleElement);
    }

    // --- Animation & State Management via CSS classes ---
    function expandController() {
        if (!isCollapsed || !speedControllerElement) return;
        isCollapsed = false;
        speedControllerElement.classList.remove('collapsed');
    }

    function collapseController() {
        if (isCollapsed || (speedControllerElement && speedControllerElement.matches(':hover'))) return;
        isCollapsed = true;
        speedControllerElement.classList.add('collapsed');
    }

    // --- UI Creation & Logic ---
    function getOrCreateSpeedController() {
        if (document.getElementById('yt-speed-controller')) return;

        speedControllerElement = document.createElement('div');
        speedControllerElement.id = 'yt-speed-controller';
        Object.assign(speedControllerElement.style, {
            position: 'fixed', bottom: '20px', left: '50%', transform: 'translateX(-50%)',
            backgroundColor: 'rgba(32, 39, 95, 0.5)', color: '#d1c2ea',
            borderRadius: '25px', zIndex: '2147483647',
            fontFamily: 'Arial, sans-serif', display: 'flex', alignItems: 'center', justifyContent: 'center',
            userSelect: 'none', backdropFilter: 'blur(10px)', webkitBackdropFilter: 'blur(10px)',
            border: '1px solid rgba(209, 194, 234, 0.2)',
            opacity: '0', overflow: 'hidden', boxSizing: 'border-box'
        });

        const innerWrapper = document.createElement('div');
        innerWrapper.className = 'yt-controller-inner-wrapper';
        Object.assign(innerWrapper.style, { display: 'flex', alignItems: 'center', gap: '10px' });

        const collapsedIndicator = document.createElement('span');
        collapsedIndicator.className = 'yt-collapsed-indicator';
        collapsedIndicator.textContent = '⌬';
        Object.assign(collapsedIndicator.style, { position: 'absolute', fontSize: '15px' });

        const decreaseSpeedButton = document.createElement('button');
        decreaseSpeedButton.textContent = '↼';
        decreaseSpeedButton.title = 'Decrease speed';
        styleControlButton(decreaseSpeedButton);
        decreaseSpeedButton.addEventListener('click', () => changeContentSpeed(-SPEED_STEP));

        speedDisplayElement = document.createElement('span');
        speedDisplayElement.textContent = `${userPreferredContentSpeed.toFixed(2)}x`;
        Object.assign(speedDisplayElement.style, { minWidth: '45px', textAlign: 'center', fontSize: '15px', fontWeight: 'bold', cursor: 'pointer' });
        speedDisplayElement.title = 'Double-click to reset speed to 1.0x';
        speedDisplayElement.addEventListener('dblclick', () => {
            userPreferredContentSpeed = RESET_SPEED;
            speedDisplayElement.textContent = `${userPreferredContentSpeed.toFixed(2)}x`;
            applySpeedToVideo();
        });

        const increaseSpeedButton = document.createElement('button');
        increaseSpeedButton.textContent = '⇀';
        increaseSpeedButton.title = 'Increase speed';
        styleControlButton(increaseSpeedButton);
        increaseSpeedButton.addEventListener('click', () => changeContentSpeed(SPEED_STEP));

        innerWrapper.appendChild(decreaseSpeedButton);
        innerWrapper.appendChild(speedDisplayElement);
        innerWrapper.appendChild(increaseSpeedButton);
        speedControllerElement.appendChild(innerWrapper);
        speedControllerElement.appendChild(collapsedIndicator);
        document.body.appendChild(speedControllerElement);

        speedControllerElement.addEventListener('mouseenter', () => {
            clearTimeout(collapseTimer);
            expandController();
        });
        speedControllerElement.addEventListener('mouseleave', () => {
            collapseTimer = setTimeout(collapseController, INACTIVITY_TIMEOUT);
        });
    }

    function styleControlButton(button) {
        Object.assign(button.style, {
            background: 'transparent', border: 'none', color: '#d1c2ea',
            cursor: 'pointer', padding: '0', fontSize: '20px', lineHeight: '1'
        });
        button.onmouseover = () => button.style.opacity = '0.7';
        button.onmouseout = () => button.style.opacity = '1';
    }

    function changeContentSpeed(delta) {
        let newSpeed = userPreferredContentSpeed + delta;
        newSpeed = Math.max(MIN_CONTENT_SPEED, Math.min(MAX_CONTENT_SPEED, newSpeed));
        userPreferredContentSpeed = parseFloat(newSpeed.toFixed(2));
        if (speedDisplayElement) {
            speedDisplayElement.textContent = `${userPreferredContentSpeed.toFixed(2)}x`;
        }
        applySpeedToVideo();
    }

    function applySpeedToVideo() {
        const videoElement = document.querySelector('.html5-main-video');
        if (videoElement && videoElement.playbackRate !== userPreferredContentSpeed) {
            videoElement.playbackRate = userPreferredContentSpeed;
        }
    }

    function initialize() {
        if (speedEnforceIntervalId) clearInterval(speedEnforceIntervalId);
        let attempts = 0;
        const maxAttempts = 20;

        function tryToStart() {
            attempts++;
            const videoPlayerExists = document.querySelector('#movie_player .html5-main-video');
            if (videoPlayerExists && document.body.contains(videoPlayerExists)) {
                injectCss();
                getOrCreateSpeedController();
                if (speedControllerElement) {
                    speedControllerElement.style.opacity = '1';
                    collapseTimer = setTimeout(collapseController, INACTIVITY_TIMEOUT);
                }
                speedEnforceIntervalId = setInterval(applySpeedToVideo, 500);
            } else if (attempts < maxAttempts) {
                setTimeout(tryToStart, 500);
            }
        }
        setTimeout(tryToStart, 250);
    }

    function cleanup() {
        clearTimeout(collapseTimer);
        if (speedEnforceIntervalId) clearInterval(speedEnforceIntervalId);
        document.getElementById('yt-speed-controller')?.remove();
        document.getElementById('yt-speed-controller-styles')?.remove();
        speedControllerElement = null;
        speedDisplayElement = null;
        isCollapsed = false;
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        initialize();
    } else {
        window.addEventListener('DOMContentLoaded', initialize, { once: true });
    }
    document.addEventListener('yt-navigate-finish', () => {
        cleanup();
        initialize();
    });

})();

Install using tamperMonkey. Hope you'll like it. Please let me know how I can improve.
 
  • Tags
    modern speed youtube
  • Top