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-08-27 提交的版本。查看 最新版本

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

QingJ © 2025

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