Adds playback speed control buttons and keyboard shortcuts to YouTube videos
// ==UserScript==
// @name YouTube Playback Speed Buttons
// @namespace https://youtube.com
// @version 1.1
// @description Adds playback speed control buttons and keyboard shortcuts to YouTube videos
// @author indistinctive
// @match https://www.youtube.com/*
// @icon https://www.google.com/s2/favicons?domain=youtube.com
// @grant none
// @run-at document-idle
// @license GNU GPLv3
// ==/UserScript==
(function() {
'use strict';
let activeButton = null;
function createSpeedControls() {
const player = document.querySelector('ytd-player');
const video = player ? player.querySelector('video') : null;
if (!video) return;
const controlsContainer = player.querySelector('.ytp-right-controls');
if (!controlsContainer) return;
const speedButtonContainer = document.createElement('div');
speedButtonContainer.style.display = 'flex';
speedButtonContainer.style.alignItems = 'center';
speedButtonContainer.style.marginRight = '10px';
speedButtonContainer.style.zIndex = '9999';
// Speeds array with desired speeds
const speeds = [1, 1.5, 2, 3];
speeds.forEach(speed => {
const button = document.createElement('button');
button.innerText = `${speed}×`;
button.style.padding = '4px 10px';
button.style.marginRight = '5px';
button.style.border = 'none';
button.style.backgroundColor = '#fff';
button.style.color = '#000';
button.style.cursor = 'pointer';
button.style.fontSize = '14px';
button.style.fontWeight = '500';
button.style.borderRadius = '5px';
button.style.transition = 'background-color 0.2s ease, color 0.2s ease';
button.style.boxShadow = 'none';
button.style.outline = 'none';
button.style.width = '40px';
button.style.display = 'flex';
button.style.justifyContent = 'center';
button.style.alignItems = 'center';
// Hover effect
button.addEventListener('mouseenter', () => {
if (button !== activeButton) {
button.style.backgroundColor = '#000';
button.style.color = '#fff';
}
});
button.addEventListener('mouseleave', () => {
if (button !== activeButton) {
button.style.backgroundColor = '#fff';
button.style.color = '#000';
}
});
// Handle button click to change playback speed
button.addEventListener('click', () => {
video.playbackRate = speed;
highlightButton(button);
});
speedButtonContainer.appendChild(button);
});
controlsContainer.style.display = 'flex';
controlsContainer.insertBefore(speedButtonContainer, controlsContainer.firstChild);
}
// Highlight the active speed button
function highlightButton(button) {
if (activeButton !== button) {
if (activeButton) {
activeButton.style.backgroundColor = '#fff';
activeButton.style.color = '#000';
}
activeButton = button;
button.style.backgroundColor = '#000';
button.style.color = '#fff';
}
}
// Listen for keyboard shortcuts
document.addEventListener('keydown', (event) => {
const player = document.querySelector('ytd-player');
const video = player ? player.querySelector('video') : null;
if (!video || event.target.tagName.toLowerCase() === 'input' || event.target.tagName.toLowerCase() === 'textarea') {
return;
}
switch (event.key.toLowerCase()) {
case 'q':
video.playbackRate = 1;
updateActiveButton(1);
break;
case 's':
video.playbackRate = 1.5;
updateActiveButton(1.5);
break;
case 'w':
video.playbackRate = 2;
updateActiveButton(2);
break;
case 'e':
video.playbackRate = 3;
updateActiveButton(3);
break;
}
});
// Update the active button based on playback speed
function updateActiveButton(speed) {
const player = document.querySelector('ytd-player');
const controlsContainer = player ? player.querySelector('.ytp-right-controls') : null;
if (!controlsContainer) return;
const buttons = controlsContainer.querySelectorAll('button');
buttons.forEach(button => {
if (button.innerText === `${speed}×`) {
highlightButton(button);
}
});
}
function waitForPlayer() {
const observer = new MutationObserver(() => {
const player = document.querySelector('ytd-player');
const controlsContainer = player ? player.querySelector('.ytp-right-controls') : null;
if (controlsContainer) {
createSpeedControls();
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
waitForPlayer();
})();