Stick YouTube Progress Bar

Stick YouTube video progress bar to the player bottom

目前为 2023-05-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Stick YouTube Progress Bar
  3. // @version 1.0.7
  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.  
  19. function main() {
  20. if (!location.pathname.startsWith('/watch') &&
  21. !location.pathname.startsWith('/live'))
  22. return
  23.  
  24. const observer = new MutationObserver((_) => {
  25. const progress = document.getElementById('stick_progress');
  26.  
  27. // 如果是直播的話就不用了
  28. const live_badge = document.querySelector(".ytp-live-badge");
  29. if (live_badge && getComputedStyle(live_badge).display !== 'none') {
  30. // TODO: 或許不用砍掉,可以直接用 css 的方式把它藏起來就好了?
  31. progress?.remove();
  32. observer.disconnect();
  33. console.log(`[${NAME}] cancaled in livestream`);
  34. return;
  35. }
  36.  
  37. // 已經建好的可以重複利用
  38. if (progress) return
  39.  
  40. // 沒什麼意義,只是拿來判斷 control bar 初始化完了沒
  41. const progress_bar = document.querySelector(".ytp-progress-list");
  42. if (!progress_bar) return;
  43.  
  44. console.log(`[${NAME}] initializing...`);
  45.  
  46. stick_progress_bar();
  47.  
  48. observer.disconnect();
  49. console.log(`[${NAME}] loaded`);
  50. });
  51. observer.observe(document.body, { childList: true, subtree: true });
  52. }
  53.  
  54. function stick_progress_bar() {
  55. inject_custom_style(`
  56.  
  57. #stick_progress {
  58. display: none;
  59. z-index: 32;
  60. position: absolute;
  61. bottom: 4px;
  62. width: 97.5%;
  63. height: 4px;
  64. margin: 0 1.25%;
  65. background-color: rgba(255, 255, 255, .2);
  66. }
  67.  
  68. .stick_play_progress, .stick_load_progress {
  69. position: absolute;
  70. width: 100%;
  71. height: 100%;
  72. }
  73.  
  74. .stick_play_progress, .stick_load_progress {
  75. transform-origin: left;
  76. transform: scaleX(0);
  77. }
  78.  
  79. .stick_load_progress {
  80. z-index: 33;
  81. background-color: rgba(255, 255, 255, .4);
  82. }
  83.  
  84. .stick_play_progress {
  85. z-index: 34;
  86. background-color: #f00;
  87. }
  88.  
  89. .ytp-autohide #stick_progress {
  90. display: block;
  91. }
  92. `);
  93.  
  94. const progress = document.createElement("div");
  95. progress.id = "stick_progress";
  96.  
  97. const play_progress = document.createElement("div");
  98. play_progress.className = "stick_play_progress";
  99. progress.append(play_progress);
  100.  
  101. const load_progress = document.createElement("div");
  102. load_progress.className = "stick_load_progress";
  103. progress.append(load_progress);
  104.  
  105. const player = document.querySelector("#movie_player");
  106. player.append(progress);
  107.  
  108. const video = document.querySelector("video");
  109. video.addEventListener("timeupdate", () => {
  110. // 只有在自動隱藏的時候才去更新進度條,不想浪費資源
  111. if (!player.classList.contains("ytp-autohide")) return;
  112.  
  113. const progress = video.currentTime / video.duration;
  114. play_progress.style.transform = `scaleX(${progress})`;
  115.  
  116. const loaded_progress = video.buffered.end(0) / video.duration;
  117. load_progress.style.transform = `scaleX(${loaded_progress})`;
  118. });
  119. }
  120.  
  121. function inject_custom_style(css) {
  122. const style = document.createElement("style");
  123. document.head.append(style);
  124. style.dataset.source = NAME;
  125. style.innerHTML = css;
  126. }
  127.  
  128. main();
  129. document.addEventListener('yt-navigate-finish', main, true);
  130.  
  131. })();
  132.