YT Speed Controller

Lightweight speed control for YouTube with persistence

  1. // ==UserScript==
  2. // @name YT Speed Controller
  3. // @namespace -/-
  4. // @version 1.0.1
  5. // @description Lightweight speed control for YouTube with persistence
  6. // @author Dataraj
  7. // @match https://www.youtube.com/*
  8. // @license MIT
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. const STORAGE_KEY = 'youtube-speed-value';
  16. const SPEED_STEP = 0.1;
  17. let speedDisplay = null;
  18. let isInitialized = false;
  19.  
  20. // Debounce function to limit execution frequency
  21. function debounce(func, wait) {
  22. let timeout;
  23. return function executedFunction(...args) {
  24. const later = () => {
  25. clearTimeout(timeout);
  26. func(...args);
  27. };
  28. clearTimeout(timeout);
  29. timeout = setTimeout(later, wait);
  30. };
  31. }
  32.  
  33. function initSpeedControl() {
  34. if (isInitialized) return;
  35.  
  36. const video = document.querySelector('video');
  37. const timeDisplay = document.querySelector('.ytp-time-display');
  38. if (!video || !timeDisplay) return;
  39.  
  40. // Create speed display if not exists
  41. if (!speedDisplay) {
  42. speedDisplay = document.createElement('span');
  43. speedDisplay.style.marginLeft = '10px';
  44. timeDisplay.appendChild(speedDisplay);
  45. }
  46.  
  47. // Apply saved speed
  48. const savedSpeed = parseFloat(localStorage.getItem(STORAGE_KEY)) || 1.0;
  49. video.playbackRate = savedSpeed;
  50. updateDisplay(savedSpeed);
  51.  
  52. // Show guide only once per session
  53. if (!sessionStorage.getItem('guide-shown')) {
  54. showTemporaryGuide();
  55. sessionStorage.setItem('guide-shown', 'true');
  56. }
  57.  
  58. isInitialized = true;
  59. }
  60.  
  61. // Simplified speed display update
  62. function updateDisplay(speed) {
  63. if (speedDisplay) {
  64. speedDisplay.textContent = speed === 1 ? '' : `${speed.toFixed(1)}x`;
  65. }
  66. }
  67.  
  68. // Optimized speed indicator
  69. function showSpeedIndicator(speed) {
  70. const bezel = document.querySelector('.ytp-bezel-text');
  71. if (!bezel) return;
  72.  
  73. const container = bezel.parentNode.parentNode;
  74. bezel.textContent = `${speed.toFixed(1)}x`;
  75. container.style.display = '';
  76.  
  77. setTimeout(() => container.style.display = 'none', 500);
  78. }
  79.  
  80. // Simplified guide
  81. function showTemporaryGuide() {
  82. const guide = document.createElement('div');
  83. guide.innerHTML = `
  84. <div style="position:fixed;bottom:20px;right:20px;background:rgba(0,0,0,0.8);
  85. color:white;padding:12px;border-radius:6px;z-index:9999;font-family:Arial">
  86. <div>Speed Controls:</div>
  87. <div>] - Speed up</div>
  88. <div>[ - Slow down</div>
  89. <div>p - Reset to 1x</div>
  90. </div>
  91. `;
  92. document.body.appendChild(guide);
  93. setTimeout(() => guide.remove(), 5000);
  94. }
  95.  
  96. // Efficient keyboard handler
  97. function handleKeypress(e) {
  98. if (document.activeElement.tagName === 'INPUT') return;
  99.  
  100. const video = document.querySelector('video');
  101. if (!video) return;
  102.  
  103. let newSpeed = video.playbackRate;
  104.  
  105. switch(e.key) {
  106. case ']':
  107. newSpeed = Math.min(16, newSpeed + SPEED_STEP);
  108. break;
  109. case '[':
  110. newSpeed = Math.max(0.1, newSpeed - SPEED_STEP);
  111. break;
  112. case 'p':
  113. newSpeed = 1.0;
  114. break;
  115. default:
  116. return;
  117. }
  118.  
  119. video.playbackRate = newSpeed;
  120. localStorage.setItem(STORAGE_KEY, newSpeed.toString());
  121. updateDisplay(newSpeed);
  122. showSpeedIndicator(newSpeed);
  123. }
  124.  
  125. // Optimized initialization
  126. function init() {
  127. // Remove any existing event listeners
  128. document.removeEventListener('keydown', handleKeypress);
  129.  
  130. // Add single event listener for keyboard controls
  131. document.addEventListener('keydown', handleKeypress);
  132.  
  133. // Debounced initialization for navigation changes
  134. const debouncedInit = debounce(() => {
  135. if (window.location.pathname.includes('/watch')) {
  136. isInitialized = false;
  137. initSpeedControl();
  138. }
  139. }, 1000);
  140.  
  141. // Listen for URL changes
  142. let lastUrl = location.href;
  143. new MutationObserver(() => {
  144. if (location.href !== lastUrl) {
  145. lastUrl = location.href;
  146. debouncedInit();
  147. }
  148. }).observe(document, {subtree: true, childList: true});
  149.  
  150. // Initial setup
  151. debouncedInit();
  152. }
  153.  
  154. // Start the script
  155. if (document.readyState === 'loading') {
  156. document.addEventListener('DOMContentLoaded', init);
  157. } else {
  158. init();
  159. }
  160. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址