YouTube - Playback Speed Slider

Adds a slider for playback speed control on YouTube videos.

Install this script?
Author's suggested script

You may also like YouTube - Volume Badge.

Install this script
  1. // ==UserScript==
  2. // @name YouTube - Playback Speed Slider
  3. // @name:fr YouTube - Curseur de vitesse de lecture
  4. // @name:es YouTube - Deslizador de velocidad de reproducción
  5. // @name:de YouTube - Wiedergabegeschwindigkeit-Schieberegler
  6. // @name:it YouTube - Cursore della velocità di riproduzione
  7. // @name:zh-CN YouTube - 播放速度滑块
  8. // @namespace https://gist.github.com/4lrick/8149bd289cf94889a97aae9732a17144
  9. // @version 1.0
  10. // @description Adds a slider for playback speed control on YouTube videos.
  11. // @description:fr Ajoute un curseur pour contrôler la vitesse de lecture des vidéos YouTube.
  12. // @description:es Agrega un deslizador para controlar la velocidad de reproducción en videos de YouTube.
  13. // @description:de Fügt einen Schieberegler zur Steuerung der Wiedergabegeschwindigkeit in YouTube-Videos hinzu.
  14. // @description:it Aggiunge un cursore per controllare la velocità di riproduzione dei video di YouTube.
  15. // @description:zh-CN 在YouTube视频中添加播放速度控制滑块。
  16. // @author 4lrick
  17. // @match https://www.youtube.com/*
  18. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  19. // @grant GM_getValue
  20. // @grant GM_setValue
  21. // @grant GM_registerMenuCommand
  22. // @grant GM_unregisterMenuCommand
  23. // @license GPL-3.0-only
  24. // ==/UserScript==
  25.  
  26. (function () {
  27. 'use strict';
  28.  
  29. const CONSTANTS = {
  30. MIN_SPEED: 0.25,
  31. MAX_SPEED: 10.0,
  32. DEFAULT_SPEED: 1.0,
  33. DEFAULT_SPEED_STEP: 0.05,
  34. };
  35.  
  36. const STORAGE_KEY = 'yt-player-speed';
  37. const REMEMBER_SPEED_KEY = 'rememberSpeed';
  38. const SPEED_STEP_KEY = 'speedStep';
  39.  
  40. let speedStep = GM_getValue(SPEED_STEP_KEY, CONSTANTS.DEFAULT_SPEED_STEP);
  41. let rememberSpeed = GM_getValue(REMEMBER_SPEED_KEY, true);
  42. let savedSpeed = parseFloat(localStorage.getItem(STORAGE_KEY)) || CONSTANTS.DEFAULT_SPEED;
  43. let rememberSpeedMenuId, speedStepMenuId;
  44. let sliderRef = null;
  45. let observer = null;
  46.  
  47. function updateRememberSpeedMenu() {
  48. if (rememberSpeedMenuId) {
  49. GM_unregisterMenuCommand(rememberSpeedMenuId);
  50. }
  51. const status = rememberSpeed ? 'ON' : 'OFF';
  52. rememberSpeedMenuId = GM_registerMenuCommand(`Remember speed (current: ${status})`, toggleRememberSpeed);
  53. }
  54.  
  55. function updateSpeedStepMenu() {
  56. if (speedStepMenuId) {
  57. GM_unregisterMenuCommand(speedStepMenuId);
  58. }
  59. speedStepMenuId = GM_registerMenuCommand(`Set speed step (current: ${speedStep})`, setSpeedStep);
  60. }
  61.  
  62. function toggleRememberSpeed() {
  63. const video = document.querySelector('video');
  64. if (!video) return;
  65.  
  66. rememberSpeed = !rememberSpeed;
  67.  
  68. if (rememberSpeed) {
  69. const currentSpeed = video.playbackRate;
  70. localStorage.setItem(STORAGE_KEY, currentSpeed);
  71. savedSpeed = currentSpeed;
  72. }
  73.  
  74. GM_setValue(REMEMBER_SPEED_KEY, rememberSpeed);
  75. updateRememberSpeedMenu();
  76. }
  77.  
  78. function setSpeedStep() {
  79. const input = prompt('Enter a new speed step (e.g., 0.25):', speedStep);
  80. const newSpeedStep = parseFloat(input);
  81.  
  82. if (!isNaN(newSpeedStep) && newSpeedStep > 0) {
  83. speedStep = newSpeedStep;
  84. GM_setValue(SPEED_STEP_KEY, speedStep);
  85. updateSpeedStepMenu();
  86. if (sliderRef) {
  87. sliderRef.step = String(speedStep);
  88. }
  89. } else {
  90. alert('Invalid input. Please enter a positive number.');
  91. }
  92. }
  93.  
  94. function applyRememberedSpeed(video) {
  95. if (rememberSpeed && video) {
  96. video.playbackRate = parseFloat(savedSpeed);
  97. }
  98. }
  99.  
  100. function initMenu() {
  101. const menuButton = document.querySelector('.ytp-settings-button');
  102. const menu = document.querySelector('.ytp-settings-menu');
  103. if (menuButton && menu) {
  104. menu.style.opacity = '0';
  105. menuButton.click();
  106. menuButton.click();
  107. menu.style.opacity = '1';
  108. }
  109. }
  110.  
  111. function formatSpeed(value) {
  112. const rounded = Math.round(value * 100) / 100;
  113. return rounded % 1 === 0 ? `${rounded.toFixed(0)}x` : `${rounded}x`;
  114. }
  115.  
  116. function handleSlider(slider, video, speedValue) {
  117. const inputListener = () => {
  118. const newSpeed = parseFloat(slider.value);
  119. video.playbackRate = newSpeed;
  120. speedValue.textContent = formatSpeed(newSpeed);
  121. if (rememberSpeed) {
  122. savedSpeed = newSpeed;
  123. localStorage.setItem(STORAGE_KEY, newSpeed);
  124. }
  125. slider.style.setProperty('--yt-slider-shape-gradient-percent', `${(newSpeed / slider.max) * 100}%`);
  126. };
  127.  
  128. const wheelListener = (e) => {
  129. e.preventDefault();
  130. const delta = e.deltaY < 0 ? speedStep : -speedStep;
  131. let newSpeed = parseFloat(slider.value) + delta;
  132. newSpeed = Math.max(parseFloat(slider.min), Math.min(parseFloat(slider.max), newSpeed));
  133. slider.value = newSpeed;
  134. video.playbackRate = newSpeed;
  135. speedValue.textContent = formatSpeed(newSpeed);
  136. if (rememberSpeed) {
  137. savedSpeed = newSpeed;
  138. localStorage.setItem(STORAGE_KEY, newSpeed);
  139. }
  140. slider.style.setProperty('--yt-slider-shape-gradient-percent', `${(newSpeed / slider.max) * 100}%`);
  141. };
  142.  
  143. slider.addEventListener('input', inputListener);
  144. slider.addEventListener('wheel', wheelListener);
  145. }
  146.  
  147. function createSlider(playbackSpeedContent, video) {
  148. playbackSpeedContent.textContent = '';
  149.  
  150. const container = document.createElement('div');
  151. container.style.display = 'flex';
  152. container.style.alignItems = 'center';
  153.  
  154. const speedValue = document.createElement('span');
  155. speedValue.textContent = formatSpeed(video.playbackRate);
  156. speedValue.style.width = '50px';
  157.  
  158. const slider = document.createElement('input');
  159. slider.type = 'range';
  160. slider.min = String(CONSTANTS.MIN_SPEED);
  161. slider.max = String(CONSTANTS.MAX_SPEED);
  162. slider.step = String(speedStep);
  163. slider.value = video.playbackRate;
  164.  
  165. slider.classList.add('ytp-input-slider');
  166. slider.style.margin = '0 10px';
  167. slider.style.setProperty('--yt-slider-shape-gradient-percent', `${(video.playbackRate / slider.max) * 100}%`);
  168.  
  169. slider.addEventListener('click', (e) => e.stopPropagation());
  170.  
  171. handleSlider(slider, video, speedValue);
  172.  
  173. container.appendChild(speedValue);
  174. container.appendChild(slider);
  175. playbackSpeedContent.appendChild(container);
  176. sliderRef = slider;
  177. }
  178.  
  179. function findPlaybackSpeedMenuItem() {
  180. const menuItem = document.querySelector('.ytp-menuitem[aria-label*="Playback speed"]');
  181. if (menuItem) return menuItem;
  182. const speedIcon = document.querySelector('.ytp-menuitem-icon svg path[d*="M10,8v8l6-4L10,8"]');
  183. return speedIcon ? speedIcon.closest('.ytp-menuitem') : null;
  184. }
  185.  
  186. function setupSliderMenuItem() {
  187. const video = document.querySelector('video');
  188. if (!video) return;
  189.  
  190. applyRememberedSpeed(video);
  191.  
  192. const playbackSpeedMenuItem = findPlaybackSpeedMenuItem();
  193.  
  194. if (!playbackSpeedMenuItem) return;
  195. const playbackSpeedContent = playbackSpeedMenuItem.querySelector('.ytp-menuitem-content');
  196.  
  197. if (playbackSpeedContent && !playbackSpeedContent.querySelector('input[type="range"]')) {
  198. createSlider(playbackSpeedContent, video);
  199. initMenu();
  200. }
  201. }
  202.  
  203. function observeVideoChanges() {
  204. if (observer) {
  205. observer.disconnect();
  206. }
  207.  
  208. observer = new MutationObserver(setupSliderMenuItem);
  209. observer.observe(document.body, { childList: true, subtree: true });
  210. }
  211.  
  212. updateSpeedStepMenu();
  213. updateRememberSpeedMenu();
  214. observeVideoChanges();
  215. })();