—Hexation—
Member
- Joined
- September 28, 2025
- Messages
- 6
- Reaction score
- 0
- Points
- 1
- Thread Author
-
- #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.