YouTube - 播放速度滑块

在YouTube视频中添加播放速度控制滑块。

当前为 2024-12-24 提交的版本,查看 最新版本

  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 isSpeedApplied = false;
  45. let sliderRef = null;
  46. let observer = null;
  47.  
  48. function updateRememberSpeedMenu() {
  49. if (rememberSpeedMenuId) {
  50. GM_unregisterMenuCommand(rememberSpeedMenuId);
  51. }
  52. const status = rememberSpeed ? 'ON' : 'OFF';
  53. rememberSpeedMenuId = GM_registerMenuCommand(`Remember speed (current: ${status})`, toggleRememberSpeed);
  54. }
  55.  
  56. function updateSpeedStepMenu() {
  57. if (speedStepMenuId) {
  58. GM_unregisterMenuCommand(speedStepMenuId);
  59. }
  60. speedStepMenuId = GM_registerMenuCommand(`Set speed step (current: ${speedStep})`, setSpeedStep);
  61. }
  62.  
  63. function toggleRememberSpeed() {
  64. const video = document.querySelector('video');
  65. if (!video) return;
  66.  
  67. rememberSpeed = !rememberSpeed;
  68.  
  69. if (rememberSpeed) {
  70. const currentSpeed = video.playbackRate;
  71. localStorage.setItem(STORAGE_KEY, currentSpeed);
  72. savedSpeed = currentSpeed;
  73. }
  74.  
  75. GM_setValue(REMEMBER_SPEED_KEY, rememberSpeed);
  76. updateRememberSpeedMenu();
  77. }
  78.  
  79. function setSpeedStep() {
  80. const input = prompt('Enter a new speed step (e.g., 0.25):', speedStep);
  81. const newSpeedStep = parseFloat(input);
  82.  
  83. if (!isNaN(newSpeedStep) && newSpeedStep > 0) {
  84. speedStep = newSpeedStep;
  85. GM_setValue(SPEED_STEP_KEY, speedStep);
  86. updateSpeedStepMenu();
  87. if (sliderRef) {
  88. sliderRef.step = String(speedStep);
  89. }
  90. } else {
  91. alert('Invalid input. Please enter a positive number.');
  92. }
  93. }
  94.  
  95. function applyRememberedSpeed(video) {
  96. if (rememberSpeed && video && !isSpeedApplied) {
  97. video.playbackRate = parseFloat(savedSpeed);
  98. isSpeedApplied = true;
  99. }
  100. }
  101.  
  102. function initMenu() {
  103. const menuButton = document.querySelector('.ytp-settings-button');
  104. const menu = document.querySelector('.ytp-settings-menu');
  105. if (menuButton && menu) {
  106. menu.style.opacity = '0';
  107. menuButton.click();
  108. menuButton.click();
  109. menu.style.opacity = '1';
  110. }
  111. }
  112.  
  113. function formatSpeed(value) {
  114. const rounded = Math.round(value * 100) / 100;
  115. return rounded % 1 === 0 ? `${rounded.toFixed(0)}x` : `${rounded}x`;
  116. }
  117.  
  118. function handleSlider(slider, video, speedValue) {
  119. const inputListener = () => {
  120. const newSpeed = parseFloat(slider.value);
  121. video.playbackRate = newSpeed;
  122. speedValue.textContent = formatSpeed(newSpeed);
  123. if (rememberSpeed) {
  124. savedSpeed = newSpeed;
  125. localStorage.setItem(STORAGE_KEY, newSpeed);
  126. }
  127. slider.style.setProperty('--yt-slider-shape-gradient-percent', `${(newSpeed / slider.max) * 100}%`);
  128. };
  129.  
  130. const wheelListener = (e) => {
  131. e.preventDefault();
  132. const delta = e.deltaY < 0 ? speedStep : -speedStep;
  133. let newSpeed = parseFloat(slider.value) + delta;
  134. newSpeed = Math.max(parseFloat(slider.min), Math.min(parseFloat(slider.max), newSpeed));
  135. slider.value = newSpeed;
  136. video.playbackRate = newSpeed;
  137. speedValue.textContent = formatSpeed(newSpeed);
  138. if (rememberSpeed) {
  139. savedSpeed = newSpeed;
  140. localStorage.setItem(STORAGE_KEY, newSpeed);
  141. }
  142. slider.style.setProperty('--yt-slider-shape-gradient-percent', `${(newSpeed / slider.max) * 100}%`);
  143. };
  144.  
  145. slider.addEventListener('input', inputListener);
  146. slider.addEventListener('wheel', wheelListener);
  147. }
  148.  
  149. function createSlider(playbackSpeedContent, video) {
  150. playbackSpeedContent.textContent = '';
  151.  
  152. const container = document.createElement('div');
  153. container.style.display = 'flex';
  154. container.style.alignItems = 'center';
  155.  
  156. const speedValue = document.createElement('span');
  157. speedValue.textContent = formatSpeed(video.playbackRate);
  158. speedValue.style.width = '50px';
  159.  
  160. const slider = document.createElement('input');
  161. slider.type = 'range';
  162. slider.min = String(CONSTANTS.MIN_SPEED);
  163. slider.max = String(CONSTANTS.MAX_SPEED);
  164. slider.step = String(speedStep);
  165. slider.value = video.playbackRate;
  166.  
  167. slider.classList.add('ytp-input-slider');
  168. slider.style.margin = '0 10px';
  169. slider.style.setProperty('--yt-slider-shape-gradient-percent', `${(video.playbackRate / slider.max) * 100}%`);
  170.  
  171. slider.addEventListener('click', (e) => e.stopPropagation());
  172.  
  173. handleSlider(slider, video, speedValue);
  174.  
  175. container.appendChild(speedValue);
  176. container.appendChild(slider);
  177. playbackSpeedContent.appendChild(container);
  178. sliderRef = slider;
  179. }
  180.  
  181. function findPlaybackSpeedMenuItem() {
  182. const menuItem = document.querySelector('.ytp-menuitem[aria-label*="Playback speed"]');
  183. if (menuItem) return menuItem;
  184. const speedIcon = document.querySelector('.ytp-menuitem-icon svg path[d*="M10,8v8l6-4L10,8"]');
  185. return speedIcon ? speedIcon.closest('.ytp-menuitem') : null;
  186. }
  187.  
  188. function setupSliderMenuItem() {
  189. const video = document.querySelector('video');
  190. if (!video) return;
  191.  
  192. applyRememberedSpeed(video);
  193.  
  194. const playbackSpeedMenuItem = findPlaybackSpeedMenuItem();
  195.  
  196. if (!playbackSpeedMenuItem) return;
  197. const playbackSpeedContent = playbackSpeedMenuItem.querySelector('.ytp-menuitem-content');
  198.  
  199. if (playbackSpeedContent && !playbackSpeedContent.querySelector('input[type="range"]')) {
  200. createSlider(playbackSpeedContent, video);
  201. initMenu();
  202. }
  203. }
  204.  
  205. function observeVideoChanges() {
  206. if (observer) {
  207. observer.disconnect();
  208. }
  209.  
  210. observer = new MutationObserver(setupSliderMenuItem);
  211. observer.observe(document.body, { childList: true, subtree: true });
  212. }
  213.  
  214. updateSpeedStepMenu();
  215. updateRememberSpeedMenu();
  216. observeVideoChanges();
  217. })();