YouTube Speed-Adjusted Time Display

Shows speed-adjusted time for YouTube videos

  1. // ==UserScript==
  2. // @name YouTube Speed-Adjusted Time Display
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Shows speed-adjusted time for YouTube videos
  6. // @author kavinned
  7. // @match https://www.youtube.com/*
  8. // @grant none
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=YouTube.com
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. let updateInterval = null;
  17.  
  18. function updateTimeDisplay() {
  19. const video = document.querySelector('video');
  20. if (!video) return;
  21.  
  22. const speedDisplayContainer = document.querySelector('.speed-adjusted-time-container');
  23. const speedIndicator = document.querySelector('.speed-indicator');
  24. if (!speedDisplayContainer || !speedIndicator) return;
  25.  
  26. const currentTime = video.currentTime;
  27. const duration = video.duration;
  28. const playbackRate = video.playbackRate;
  29.  
  30. // Only update if we have valid numbers
  31. if (isNaN(currentTime) || isNaN(duration) || isNaN(playbackRate) || playbackRate === 0) return;
  32.  
  33. const adjustedCurrentTime = currentTime / playbackRate;
  34. const adjustedDuration = duration / playbackRate;
  35.  
  36. function formatTime(seconds) {
  37. if (isNaN(seconds) || !isFinite(seconds)) return "0:00";
  38.  
  39. const hours = Math.floor(seconds / 3600);
  40. const minutes = Math.floor((seconds % 3600) / 60);
  41. const secs = Math.floor(seconds % 60);
  42.  
  43. if (hours > 0) {
  44. return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  45. } else {
  46. return `${minutes}:${secs.toString().padStart(2, '0')}`;
  47. }
  48. }
  49.  
  50. try {
  51. // Update our custom display with the adjusted time format matching YouTube's
  52. speedDisplayContainer.textContent = `${formatTime(adjustedCurrentTime)} / ${formatTime(adjustedDuration)}`;
  53.  
  54. // Update the speed indicator
  55. speedIndicator.textContent = `${playbackRate}×`;
  56.  
  57. // Only show our display if playback rate is not 1x
  58. const displayElements = document.querySelectorAll('.speed-time-wrapper');
  59. displayElements.forEach(el => {
  60. el.style.display = playbackRate !== 1 ? 'flex' : 'none';
  61. });
  62. } catch (e) {
  63. console.error("Error updating time display:", e);
  64. }
  65. }
  66.  
  67. function createSpeedTimeDisplay() {
  68. // Remove any existing display first
  69. const existingDisplay = document.querySelector('.speed-time-wrapper');
  70. if (existingDisplay) existingDisplay.remove();
  71.  
  72. const timeDisplay = document.querySelector('.ytp-time-display');
  73. if (!timeDisplay) return;
  74.  
  75. // Create wrapper for both time display and speed indicator
  76. const wrapper = document.createElement('div');
  77. wrapper.className = 'speed-time-wrapper';
  78. wrapper.style.display = 'none'; // Hidden by default, only show when speed isn't 1x
  79. wrapper.style.alignItems = 'center';
  80. wrapper.style.marginRight = '10px';
  81. wrapper.style.height = '1.5em'; // Set a fixed height that's less than the control bar height
  82. wrapper.style.lineHeight = '1.5em'; // Match line height to the height
  83. wrapper.style.alignSelf = 'center'; // Center vertically within parent
  84. wrapper.style.gap = '0.3em';
  85.  
  86. // Create container for time display
  87. const speedDisplayContainer = document.createElement('div');
  88. speedDisplayContainer.className = 'speed-adjusted-time-container';
  89. speedDisplayContainer.style.color = 'white';
  90. speedDisplayContainer.style.fontSize = '1em';
  91. speedDisplayContainer.style.borderRadius = '4px';
  92. speedDisplayContainer.style.backgroundColor = 'rgba(33, 33, 33, 0.8)';
  93. speedDisplayContainer.style.border = '1px solid rgba(255, 255, 255, 0.2)';
  94. speedDisplayContainer.style.borderRight = 'none';
  95. speedDisplayContainer.style.padding = '4px 4px';
  96. speedDisplayContainer.style.height = '100%';
  97. speedDisplayContainer.style.display = 'flex';
  98. speedDisplayContainer.style.alignItems = 'center'; // Center text vertically
  99.  
  100. // Create speed indicator
  101. const speedIndicator = document.createElement('div');
  102. speedIndicator.className = 'speed-indicator';
  103. speedIndicator.style.color = 'white';
  104. speedIndicator.style.fontSize = '1em';
  105. speedIndicator.style.borderRadius = '4px';
  106. speedIndicator.style.fontWeight = 'bold';
  107. speedIndicator.style.backgroundColor = '#5b8266';
  108. speedIndicator.style.border = '1px solid rgba(255, 255, 255, 0.2)';
  109. speedIndicator.style.borderLeft = 'none';
  110. speedIndicator.style.padding = '4px 4px';
  111. speedIndicator.style.height = '100%';
  112. speedIndicator.style.display = 'flex';
  113. speedIndicator.style.alignItems = 'center'; // Center text vertically
  114.  
  115. // Assemble elements
  116. wrapper.appendChild(speedDisplayContainer);
  117. wrapper.appendChild(speedIndicator);
  118.  
  119. // Insert before the time display for left positioning
  120. timeDisplay.parentNode.insertBefore(wrapper, timeDisplay);
  121. return speedDisplayContainer;
  122. }
  123.  
  124. function startUpdates() {
  125. // Only start interval if not already running
  126. if (!updateInterval) {
  127. createSpeedTimeDisplay();
  128. updateInterval = setInterval(updateTimeDisplay, 500);
  129. }
  130. }
  131.  
  132. function stopUpdates() {
  133. if (updateInterval) {
  134. clearInterval(updateInterval);
  135. updateInterval = null;
  136.  
  137. // Clean up our display
  138. const existingDisplay = document.querySelector('.speed-time-wrapper');
  139. if (existingDisplay) existingDisplay.remove();
  140. }
  141. }
  142.  
  143. // Watch for page navigation
  144. function checkForVideoPage() {
  145. if (window.location.pathname === '/watch') {
  146. startUpdates();
  147. } else {
  148. stopUpdates();
  149. }
  150. }
  151.  
  152. // Check initially
  153. checkForVideoPage();
  154.  
  155. // Listen for navigation events
  156. window.addEventListener('yt-navigate-start', stopUpdates);
  157. window.addEventListener('yt-navigate-finish', checkForVideoPage);
  158.  
  159. // Create a better observer for YouTube's player
  160. const playerObserver = new MutationObserver(() => {
  161. if (window.location.pathname === '/watch') {
  162. if (document.querySelector('video') && !document.querySelector('.speed-time-wrapper')) {
  163. createSpeedTimeDisplay();
  164. updateTimeDisplay();
  165. }
  166. }
  167. });
  168.  
  169. // Observe just the player area for better performance
  170. const observeTarget = document.querySelector('#player') || document.body;
  171. playerObserver.observe(observeTarget, {
  172. childList: true,
  173. subtree: true
  174. });
  175.  
  176. // Listen for playback rate changes
  177. document.addEventListener('ratechange', () => {
  178. updateTimeDisplay();
  179. // Make sure display exists whenever playback rate changes
  180. if (!document.querySelector('.speed-time-wrapper')) {
  181. createSpeedTimeDisplay();
  182. }
  183. }, true);
  184.  
  185. // Clean up when leaving the page
  186. window.addEventListener('beforeunload', () => {
  187. stopUpdates();
  188. playerObserver.disconnect();
  189. });
  190. })();

QingJ © 2025

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