Stick YouTube Progress Bar

Stick YouTube video progress bar to the player bottom

目前为 2023-11-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Stick YouTube Progress Bar
  3. // @version 1.0.8
  4. // @match https://www.youtube.com/**
  5. // @author peng-devs
  6. // @namespace https://greasyfork.org/users/57176
  7. // @description Stick YouTube video progress bar to the player bottom
  8. // @icon https://www.youtube.com/s/desktop/c1d331ff/img/favicon_48x48.png
  9. // @grant none
  10. // @allFrames true
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. const NAME = "Stick YouTube Progress Bar";
  18. const UPDATE_INTERVAL = 500;
  19. const SMOOTH_ANIMATION = false;
  20.  
  21. let INTERVAL;
  22. function main() {
  23. if (!location.pathname.startsWith('/watch') &&
  24. !location.pathname.startsWith('/live'))
  25. return
  26.  
  27. if (INTERVAL) {
  28. INTERVAL = undefined;
  29. }
  30.  
  31. const observer = new MutationObserver((_) => {
  32. const progress = document.getElementById('stick_progress');
  33.  
  34. // 如果是直播的話就不用了
  35. const live_badge = document.querySelector(".ytp-live-badge");
  36. if (live_badge && getComputedStyle(live_badge).display !== 'none') {
  37. // TODO: 或許不用砍掉,可以直接用 css 的方式把它藏起來就好了?
  38. progress?.remove();
  39. observer.disconnect();
  40. console.log(`[${NAME}] cancaled in livestream`);
  41. return;
  42. }
  43.  
  44. // 已經建好的可以重複利用
  45. if (progress) return
  46.  
  47. // 判斷 youtube player 初始化完了沒
  48. const intitailized = document.querySelector("video.video-stream");
  49. if (!intitailized) return;
  50.  
  51. console.log(`[${NAME}] initializing...`);
  52.  
  53. INTERVAL = stick_progress_bar();
  54.  
  55. observer.disconnect();
  56. console.log(`[${NAME}] loaded`);
  57. });
  58. observer.observe(document.body, { childList: true, subtree: true });
  59. }
  60.  
  61. function stick_progress_bar() {
  62. inject_custom_style(`
  63.  
  64. #stick_progress {
  65. display: none;
  66. z-index: 32;
  67. position: absolute;
  68. bottom: 4px;
  69. width: 97.5%;
  70. height: 4px;
  71. margin: 0 1.25%;
  72. background-color: rgba(255, 255, 255, .2);
  73. }
  74.  
  75. .stick_play_progress, .stick_load_progress {
  76. position: absolute;
  77. width: 100%;
  78. height: 100%;
  79. }
  80.  
  81. .stick_play_progress, .stick_load_progress {
  82. transform-origin: left;
  83. ${SMOOTH_ANIMATION ? `transition: all ${UPDATE_INTERVAL - 50}ms linear;` : ''}
  84. transform: scaleX(0);
  85. }
  86.  
  87. .stick_load_progress {
  88. z-index: 33;
  89. background-color: rgba(255, 255, 255, .4);
  90. }
  91.  
  92. .stick_play_progress {
  93. z-index: 34;
  94. background-color: #f00;
  95. }
  96.  
  97. .ytp-autohide #stick_progress {
  98. display: block;
  99. }
  100. `);
  101.  
  102. const progress = document.createElement("div");
  103. progress.id = "stick_progress";
  104.  
  105. const play_progress = document.createElement("div");
  106. play_progress.className = "stick_play_progress";
  107. progress.append(play_progress);
  108.  
  109. const load_progress = document.createElement("div");
  110. load_progress.className = "stick_load_progress";
  111. progress.append(load_progress);
  112.  
  113. const player = document.querySelector("#movie_player");
  114. player.append(progress);
  115.  
  116. const video = document.querySelector("video.video-stream");
  117. const interval = setInterval(() => {
  118. // 只有在自動隱藏的時候才去更新進度條,不想浪費資源
  119. if (!player.classList.contains("ytp-autohide")) return;
  120.  
  121. const progress = video.currentTime / video.duration;
  122. play_progress.style.transform = `scaleX(${progress})`;
  123.  
  124. const loaded_progress = video.buffered.end(0) / video.duration;
  125. load_progress.style.transform = `scaleX(${loaded_progress})`;
  126. }, UPDATE_INTERVAL);
  127.  
  128. return interval;
  129. }
  130.  
  131. function inject_custom_style(css) {
  132. const style = document.createElement("style");
  133. document.head.append(style);
  134. style.dataset.source = NAME;
  135. style.innerHTML = css;
  136. }
  137.  
  138. main();
  139. document.addEventListener('yt-navigate-finish', main, true);
  140.  
  141. })();
  142.