YouTube Permanent ProgressBar

Keeps YouTube progress bar visible all the time.

  1. // ==UserScript==
  2. // @name YouTube Permanent ProgressBar
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3.8
  5. // @description Keeps YouTube progress bar visible all the time.
  6. // @author ChromiaCat
  7. // @match *://www.youtube.com/*
  8. // @license MIT
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. "use strict";
  13.  
  14. var style = document.createElement('style');
  15. var to = { createHTML: s => s },
  16. tp = window.trustedTypes?.createPolicy ? trustedTypes.createPolicy("", to) : to,
  17. html = s => tp.createHTML(s);
  18. style.type = 'text/css';
  19. style.innerHTML = html(
  20. '.ytp-autohide .ytp-chrome-bottom{opacity:1!important;display:block!important}' +
  21. '.ytp-autohide .ytp-chrome-bottom .ytp-progress-bar-container{bottom:-1px!important}' +
  22. '.ytp-autohide .ytp-chrome-bottom .ytp-chrome-controls{opacity:0!important}' +
  23. '.ytp-progress-bar-container:not(:hover) .ytp-scrubber-container{display:none!important}'
  24. );
  25. document.getElementsByTagName('head')[0].appendChild(style);
  26.  
  27. var permanentProgressBar = {
  28. options: {
  29. // To change bar update rate, go to line 161.
  30. UPDATE_VIDEO_TIMER: true,
  31. UPDATE_PROGRESSBAR: true,
  32. UPDATE_BUFFERBAR: true,
  33. },
  34.  
  35. // Converts current video time to a human-readable format.
  36. prettifyVideoTime: function(video) {
  37. let seconds = "" + Math.floor(video.currentTime % 60);
  38. let minutes = "" + Math.floor((video.currentTime % 3600) / 60);
  39. let hours = "" + Math.floor(video.currentTime / 3600);
  40. if (video.currentTime / 60 > 60) {
  41. return `${hours}:${minutes.padStart(2, '0')}:${seconds.padStart(2, '0')}`;
  42. } else {
  43. return `${minutes}:${seconds.padStart(2, '0')}`;
  44. }
  45. },
  46.  
  47. // For progress mode, return current time; for buffer mode, return the end of the buffered range that contains currentTime.
  48. getDuration: function(video, type) {
  49. if (type === "PROGRESSBAR") {
  50. return video.currentTime;
  51. } else if (type === "BUFFERBAR") {
  52. if (video.buffered.length > 0) {
  53. for (let i = 0; i < video.buffered.length; i++) {
  54. if (video.currentTime >= video.buffered.start(i) && video.currentTime <= video.buffered.end(i)) {
  55. return video.buffered.end(i);
  56. }
  57. }
  58. }
  59. return 0;
  60. }
  61. },
  62.  
  63. // Updates the current time display on the player.
  64. updateCurrentTimeField: function(player) {
  65. const video = player.querySelector("video");
  66. const currentTimeEl = player.querySelector(".ytp-time-current");
  67. if (!video || !currentTimeEl) return;
  68. currentTimeEl.innerText = permanentProgressBar.prettifyVideoTime(video);
  69. },
  70.  
  71. // For non-chaptered videos, update the progress and buffer bars directly.
  72. updateOverallProgressBar: function(player) {
  73. const video = player.querySelector("video");
  74. const progressBar = player.querySelector(".ytp-play-progress");
  75. const bufferBar = player.querySelector(".ytp-load-progress");
  76. if (!video || !progressBar || !bufferBar) return;
  77. if (!video.duration) return;
  78.  
  79. let progressRatio = video.currentTime / video.duration;
  80. let bufferRatio = this.getDuration(video, "BUFFERBAR") / video.duration;
  81.  
  82. progressBar.style.transform = `scaleX(${Math.min(1, progressRatio.toFixed(5))})`;
  83. bufferBar.style.transform = `scaleX(${Math.min(1, bufferRatio.toFixed(5))})`;
  84. },
  85.  
  86. // For chaptered videos, update each chapter element directly based on current time.
  87. updateProgressBarWithChapters: function(player, type) {
  88. const video = player.querySelector("video");
  89. if (!video || isNaN(video.duration)) return;
  90.  
  91. // Get the chapter elements and corresponding progress elements.
  92. const chapterElements = player.getElementsByClassName("ytp-progress-bar-padding");
  93. let chapterProgressEls;
  94. if (type === "PROGRESSBAR") {
  95. chapterProgressEls = player.getElementsByClassName("ytp-play-progress");
  96. } else if (type === "BUFFERBAR") {
  97. chapterProgressEls = player.getElementsByClassName("ytp-load-progress");
  98. }
  99. if (!chapterElements || !chapterProgressEls) return;
  100.  
  101. // Compute total width of the progress bar (sum of all chapter widths)
  102. let totalWidth = 0;
  103. for (let i = 0; i < chapterElements.length; i++) {
  104. totalWidth += chapterElements[i].offsetWidth;
  105. }
  106. const durationWidthRatio = video.duration / totalWidth;
  107.  
  108. let accumulatedWidth = 0;
  109. for (let i = 0; i < chapterElements.length; i++) {
  110. const chapterWidth = chapterElements[i].offsetWidth;
  111. const chapterEndTime = durationWidthRatio * (accumulatedWidth + chapterWidth);
  112. const chapterStartTime = durationWidthRatio * accumulatedWidth;
  113. let currentTimeForType = this.getDuration(video, type);
  114. let ratio;
  115. if (currentTimeForType >= chapterEndTime) {
  116. ratio = 1;
  117. } else if (currentTimeForType < chapterStartTime) {
  118. ratio = 0;
  119. } else {
  120. ratio = (currentTimeForType - chapterStartTime) / (chapterEndTime - chapterStartTime);
  121. }
  122. chapterProgressEls[i].style.transform = `scaleX(${Math.min(1, ratio.toFixed(5))})`;
  123. accumulatedWidth += chapterWidth;
  124. }
  125. },
  126.  
  127. // The main update function which selects chapter-mode or overall mode.
  128. update: function() {
  129. const player = document.querySelector(".html5-video-player");
  130. if (!player) return;
  131.  
  132. if (this.options.UPDATE_VIDEO_TIMER) {
  133. this.updateCurrentTimeField(player);
  134. }
  135.  
  136. // If chapter elements exist, update chapter-mode; otherwise use overall mode.
  137. let chapterElements = player.getElementsByClassName("ytp-progress-bar-padding");
  138. if (chapterElements.length > 0) {
  139. if (this.options.UPDATE_PROGRESSBAR) {
  140. this.updateProgressBarWithChapters(player, "PROGRESSBAR");
  141. }
  142. if (this.options.UPDATE_BUFFERBAR) {
  143. this.updateProgressBarWithChapters(player, "BUFFERBAR");
  144. }
  145. } else {
  146. this.updateOverallProgressBar(player);
  147. }
  148. },
  149.  
  150. // The update loop runs on each animation frame.
  151. // Update loop code with update rate settings improved by https://gf.qytechs.cn/en/users/475505-tettedev
  152. updateLoopThrottled: function() {
  153. const loop = (ups) => {
  154. setTimeout(() => {
  155. permanentProgressBar.update();
  156. requestAnimationFrame(permanentProgressBar.updateLoop);
  157. }, 1000 / ups);
  158. };
  159.  
  160. const updateRate = 20; // How many per second. Floats are allowed e.g. 4.20
  161. loop(updateRate);
  162. },
  163.  
  164. updateLoop: function() {
  165. permanentProgressBar.update();
  166. requestAnimationFrame(permanentProgressBar.updateLoop);
  167. },
  168.  
  169. start: function() {
  170. requestAnimationFrame(this.updateLoopThrottled); // (this.updateLoop) Happens every aniamtion frame,
  171. // (this.updateLoopThrottled) is configurable to run slower but may cause jittering if YT's code updates the bar at the same time e.g. when exposing the player's controls.
  172. // I'm not sure how to disable YT's bar updating code to allow flawless bar updating for slower loop code, if you know how let me know.
  173. }
  174. };
  175.  
  176. permanentProgressBar.start();
  177. })();

QingJ © 2025

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