Change video playback speed with hotkeys.
// ==UserScript==
// @name Video Speed Controller
// @icon https://www.favicon.cc/favicon/39/699/favicon.png
// @namespace
// @version 1.0
// @description Change video playback speed with hotkeys.
// @author BlueAG
// @license MIT
// @match *://*/*
// @grant none
// @run-at document-idle
// @namespace https://greasyfork.org/users/1452677
// ==/UserScript==
(function() {
'use strict';
// Speed step for increase/decrease
const SPEED_STEP = 0.25;
const MIN_SPEED = 0.25;
const MAX_SPEED = 10.0;
// Show toast notification
function showToast(text) {
let toast = document.getElementById('tm-speed-toast');
if (!toast) {
toast = document.createElement('div');
toast.id = 'tm-speed-toast';
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(0,0,0,0.8);
color: #fff;
padding: 10px 16px;
border-radius: 8px;
font-family: Arial, sans-serif;
font-size: 16px;
font-weight: bold;
z-index: 999999;
transition: opacity 0.3s;
pointer-events: none;
`;
document.body.appendChild(toast);
}
toast.textContent = text;
toast.style.opacity = '1';
clearTimeout(toast._timeout);
toast._timeout = setTimeout(() => toast.style.opacity = '0', 1000);
}
// Get all video elements on page
function getVideos() {
return Array.from(document.querySelectorAll('video, audio'));
}
// Set speed for all videos
function setSpeed(speed) {
const videos = getVideos();
if (videos.length === 0) return;
// Clamp speed
speed = Math.max(MIN_SPEED, Math.min(MAX_SPEED, speed));
videos.forEach(v => {
v.playbackRate = speed;
});
showToast(`${speed.toFixed(2)}x`);
}
// Adjust speed by delta
function adjustSpeed(delta) {
const videos = getVideos();
if (videos.length === 0) return;
const currentSpeed = videos[0].playbackRate;
setSpeed(currentSpeed + delta);
}
// Reset speed to 1.0x
function resetSpeed() {
setSpeed(1.0);
}
// Hotkey listener
document.addEventListener('keydown', (e) => {
// Ignore if typing in input/textarea
if (e.target.matches('input, textarea, [contenteditable="true"]')) return;
switch(e.key) {
case ']': // increase speed
case ']':
adjustSpeed(SPEED_STEP);
e.preventDefault();
break;
case '[': // decrease speed
case '[':
adjustSpeed(-SPEED_STEP);
e.preventDefault();
break;
case '0': // reset to 1.0x
resetSpeed();
e.preventDefault();
break;
case 'p': // pause/play toggle
case 'P':
getVideos().forEach(v => {
if (v.paused) {
v.play();
showToast('▶ Play');
} else {
v.pause();
showToast('⏸ Pause');
}
});
e.preventDefault();
break;
case 'z': // mute/unmute toggle
case 'Z':
getVideos().forEach(v => {
v.muted = !v.muted;
showToast(v.muted ? '🔇 Muted' : '🔊 Unmuted');
});
e.preventDefault();
break;
}
});
// Apply 1.0x to new videos that load later
const observer = new MutationObserver(() => {
// Don't auto-set here or it'll override user changes
// But we could show current speed if needed
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();