Add YouTube Video Progress

Add progress bars (or dots) at bottom of YouTube video and progress text on the video page. On the progress text, the current video quality will have a "+" suffix if there's a higher one available. Hovering the mouse cursor onto the video quality text will show the current and available video quality IDs and short description.

当前为 2019-09-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Add YouTube Video Progress
  3. // @namespace https://gf.qytechs.cn/en/users/85671-jcunews
  4. // @version 1.6.20
  5. // @license GNU AGPLv3
  6. // @author jcunews
  7. // @description Add progress bars (or dots) at bottom of YouTube video and progress text on the video page. On the progress text, the current video quality will have a "+" suffix if there's a higher one available. Hovering the mouse cursor onto the video quality text will show the current and available video quality IDs and short description.
  8. // @match https://www.youtube.com/*
  9. // @grant none
  10. // @run-at document-start
  11. // ==/UserScript==
  12.  
  13. //===== CONFIGURATION BEGIN =====
  14.  
  15. var progressbarDotStyle = false; //show graphical progress as dots instead of bars
  16. var progressbarDotStyleSize = 4; //Dot size (for width & height) in pixels if dot style is enabled
  17. var progressbarHeight = 2; //in pixels
  18. var progressbarColor = "rgb(0, 0, 0, 0.3)"; //e.g. opaque: "#fff" or "#e0e0e0" or "cyan"; or semi-transparent: "rgb(0, 0, 0, 0.3)" (i.e. 30% opaque)
  19. var progressbarElapsedColor = "#f00";
  20. var progressbarBufferColor = "#37f";
  21. var contentLoadProcessDelay = 0; //number of milliseconds before processing dynamically loaded contents (increase if slow network/browser)
  22. var progressTextStyles = "position:absolute;bottom:-.9rem;border:1px solid #ccc;border-radius:4px;padding:2px;background:#eee;text-align:center;font-size:9pt;line-height:normal;";
  23. //styles override for progress text in YouTube Dark Mode
  24. var progressTextStylesDark = "border:1px solid #bbb;background:#111;color:#bbb";
  25.  
  26. //===== CONFIGURATION END =====
  27.  
  28. var timerWaitInfo, timerProgressMonitor, timerWaitPlayer, timerDoubleCheck, vplayer, eleProgressText;
  29. var resNums = {
  30. "light" : "144p", //(old ID)
  31. "tiny" : "144p",
  32. "small" : "240p",
  33. "medium" : "360p", //nHD
  34. "large" : "480p", //WNTSC
  35. "hd720" : "720p", //HD 1K
  36. "hd1080" : "1080p", //FHD 2K
  37. "hd1440" : "1440p", //QHD
  38. "hd2160" : "2160p", //UHD 4K
  39. "hd2880" : "2880p", //UHD+ 5K
  40. "highres": "4320p", //FUHD 8K (YouTube's highest resolution [2019 April])
  41. "hd6480" : "6480p", //(fictional ID for 12K. Just in case...)
  42. "hd8640" : "8640p" //(fictional ID for QUHD 16K. Just in case...)
  43. };
  44. var resDescs = {
  45. "light" : "light\xa0(144p)",
  46. "tiny" : "tiny\xa0(144p)",
  47. "small" : "small\xa0(240p)",
  48. "medium" : "medium\xa0(360p nHD)",
  49. "large" : "large\xa0(480p WNTSC)",
  50. "hd720" : "hd720\xa0(720p HD 1K)",
  51. "hd1080" : "hd1080\xa0(1080p FHD 2K)",
  52. "hd1440" : "hd1440\xa0(1440p QHD)",
  53. "hd2160" : "hd2160\xa0(2160p UHD 4K)",
  54. "hd2880" : "hd2880\xa0(2880p UHD+ 5K)",
  55. "highres": "highres\xa0(4320p FUHD 8K)",
  56. "hd6480" : "hd6480\xa0(6480p 12K)",
  57. "hd8640" : "hd8640\xa0(8640p QUHD 16K)"
  58. };
  59. var fmts = [
  60. ['3GP', 'MP4V', [13,17,36]],
  61. ['FLV', 'H263', [5,6]],
  62. ['FLV', 'H264', [34,35]],
  63. ['MP4', 'H264', [18,22,37,38,59,78,82,83,84,85,91,92,93,94,95,96,132,133,134,135,136,137,138,151,160,212,264,266,298,299]],
  64. ['WebM', 'VP8', [43,44,45,46,100,101,102,167,168,169,170,218,219]],
  65. ['WebM', 'VP9', [242,243,244,245,246,247,248,271,272,278,302,303,308,313,315]],
  66. ['M4A', 'AAC', [139,140,141,256,258]],
  67. ['M4A', 'DTS-ES', [325]],
  68. ['M4A', 'AC-3', [328]],
  69. ['WebM', 'Vorbis', [171,172]],
  70. ['WebM', 'Opus', [249,250,251]]
  71. ];
  72. var fmtMaps = {};
  73. fmts.forEach(a => a[2].forEach(f => fmtMaps[f] = [a[0], a[1]]));
  74.  
  75. function processInfo() {
  76. if (window.vidprogress || (location.pathname !== "/watch")) return;
  77. clearTimeout(timerWaitInfo);
  78. (function waitInfo(a) {
  79. if (a = document.querySelector("#info-contents #info-text")) {
  80. eleProgressText = document.createElement("DIV");
  81. eleProgressText.id = "vidprogress";
  82. eleProgressText.innerHTML = '<span id="curq" style="font-weight:500"></span><span id="curtime" style="display:inline-block;margin-left:1ex"></span>';
  83. eleProgressText.style.cssText = progressTextStyles + (document.documentElement.attributes["dark"] ? progressTextStylesDark : "");
  84. a.parentNode.insertBefore(eleProgressText, a);
  85. a.parentNode.style.position = "relative";
  86. } else timerWaitInfo = setTimeout(waitInfo, 200);
  87. })();
  88. }
  89.  
  90. function processPlayer() {
  91. function zerolead(n){
  92. return n > 9 ? n : "0" + n;
  93. }
  94.  
  95. function sec2hms(sec) {
  96. var c = sec % 60, d = Math.floor(sec / 60);
  97. return (d >= 60 ? zerolead(Math.floor(d / 60)) + ":" : "") + zerolead(d % 60) + ":" + zerolead(c);
  98. }
  99.  
  100. function getPlayer() {
  101. return (vplayer = document.querySelector(".html5-video-player"));
  102. }
  103.  
  104. function updProgress(a, b, c, d, e, f, g, l){
  105. if (eleProgressText) eleProgressText.style.cssText = progressTextStyles + (document.documentElement.attributes["dark"] ? progressTextStylesDark : "");
  106. a = getPlayer();
  107. if (a && window.vidprogress2b && a.getCurrentTime) try {
  108. if (window.curtime) try {
  109. b = a.getPlaybackQuality();
  110. c = resNums[b] || b;
  111. (d = a.getAvailableQualityLevels()).pop();
  112. curq.textContent = c + (d.indexOf(b) > 0 ? "+" : "");
  113. e = a.getVideoStats();
  114. g = fmtMaps[e.afmt] || ("a" + e.afmt);
  115. if (e.fmt) { //has video
  116. if (f = fmtMaps[e.fmt]) {
  117. f = `${f[0]} ${f[1]}`;
  118. } else f = "vid" + e.fmt;
  119. if (e.afmt) { //video & audio
  120. if (g = fmtMaps[e.afmt]) {
  121. e = ` [${f} ${g[1]}]`;
  122. } else e = ` [${f} aud${e.afmt}]`;
  123. } else { //no audio. video only
  124. e = ` [${f}]`;
  125. }
  126. } else if (e.afmt) { //no video. audio only
  127. if (f = fmtMaps[e.afmt]) {
  128. e = ` [${f[0]} ${f[1]}]`;
  129. } else e = ` [aud${e.afmt}]`;
  130. } else e = "";
  131. curq.title = `Current: ${resDescs[b] || b}${e} (${a.offsetWidth}x${a.offsetHeight} viewport)\nAvailable: ${d.map(b => resDescs[b] || b).join(", ")}`;
  132. } catch(b) {
  133. curq.textContent = "???";
  134. curq.title = "";
  135. }
  136. b = a.getCurrentTime();
  137. if (b >= 0) {
  138. l = a.getDuration();
  139. if (!a.getVideoData().isLive) {
  140. if (window.curtime) {
  141. curtime.textContent = sec2hms(Math.floor(b)) + " / " + sec2hms(Math.floor(l)) + " (" + Math.floor(b * 100 / l) + "%)";
  142. }
  143. if (progressbarDotStyle) {
  144. vidprogress2b.style.left = Math.ceil((b / l) * vidprogress2.offsetWidth) + "px";
  145. vidprogress2c.style.left = Math.ceil((a.getVideoBytesLoaded() / a.getVideoBytesTotal()) * vidprogress2.offsetWidth) + "px";
  146. } else {
  147. vidprogress2b.style.width = Math.ceil((b / l) * vidprogress2.offsetWidth) + "px";
  148. vidprogress2c.style.width = Math.ceil((a.getVideoBytesLoaded() / a.getVideoBytesTotal()) * vidprogress2.offsetWidth) + "px";
  149. }
  150. } else {
  151. if (window.curtime) curtime.textContent = "LIVE";
  152. if (progressbarDotStyle) {
  153. vidprogress2b.style.left = "100%";
  154. } else vidprogress2b.style.width = "100%";
  155. }
  156. } else throw 0;
  157. } catch(a) {
  158. if (window.curtime) curtime.textContent = "???";
  159. if (progressbarDotStyle) {
  160. vidprogress2b.style.left = "0px";
  161. vidprogress2c.style.left = "0px";
  162. } else {
  163. vidprogress2b.style.width = "0px";
  164. vidprogress2c.style.width = "0px";
  165. }
  166. }
  167. }
  168.  
  169. function resumeProgressMonitor() {
  170. if (timerProgressMonitor) return;
  171. updProgress();
  172. timerProgressMonitor = setInterval(updProgress, 200);
  173. }
  174.  
  175. function pauseProgressMonitor() {
  176. clearInterval(timerProgressMonitor);
  177. timerProgressMonitor = 0;
  178. updProgress();
  179. }
  180.  
  181. clearInterval(timerProgressMonitor);
  182. timerProgressMonitor = 0;
  183. clearTimeout(timerWaitPlayer);
  184. timerWaitPlayer = 0;
  185. clearInterval(timerDoubleCheck);
  186. timerDoubleCheck = 0;
  187. (function waitPlayer(v) {
  188. if (!window.vidprogress2 && getPlayer() && (a = vplayer.parentNode.querySelector("video"))) {
  189. b = document.createElement("DIV");
  190. b.id = "vidprogress2";
  191. b.style.cssText = `opacity:.66;position:absolute;z-index:10;bottom:0;width:100%;height:${
  192. progressbarDotStyle ? progressbarDotStyleSize : progressbarHeight}px;background:${progressbarColor}`;
  193. v = progressbarDotStyle ? "width:" + progressbarDotStyleSize + "px;margin-left:-" + Math.floor(progressbarDotStyleSize / 2) + "px;" : "";
  194. b.innerHTML = `<div id="vidprogress2c" style="position:absolute;${v}height:100%;background:${progressbarBufferColor}"></div>
  195. <div id="vidprogress2b" style="position:absolute;${v}height:100%;background:${progressbarElapsedColor}"></div>`;
  196. vplayer.appendChild(b);
  197. if (vplayer.getPlayerState() === 1) resumeProgressMonitor();
  198. //useful: onLoadedMetadata(), onStateChange(state), onPlayVideo(info), onReady(playerApi), onVideoAreaChange(), onVideoDataChange(info)
  199. //states: -1=notReady, 0=ended, 1=playing, 2=paused, 3=ready, 4=???, 5=notAvailable?
  200. vplayer.addEventListener("onLoadedMetadata", resumeProgressMonitor);
  201. vplayer.addEventListener("onStateChange", function(state) {
  202. if (state === 1) {
  203. resumeProgressMonitor();
  204. } else pauseProgressMonitor();
  205. });
  206. } else timerWaitPlayer = setTimeout(waitPlayer, 200);
  207. })();
  208.  
  209. function doubleCheck() {
  210. if (getPlayer() && vplayer.getPlayerState) {
  211. if (vplayer.getPlayerState() === 1) {
  212. resumeProgressMonitor();
  213. } else pauseProgressMonitor();
  214. }
  215. }
  216. if (!timerDoubleCheck) timerDoubleCheck = setInterval(doubleCheck, 500);
  217. }
  218.  
  219. addEventListener("yt-page-data-updated", processInfo);
  220. addEventListener("yt-player-released", processPlayer);
  221. addEventListener("load", function() {
  222. processInfo();
  223. processPlayer();
  224. });
  225. addEventListener("spfprocess", function() {
  226. setTimeout(function() {
  227. processInfo();
  228. processPlayer();
  229. }, contentLoadProcessDelay);
  230. });

QingJ © 2025

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