YouTube Volume Mouse Controller

Control YouTube video volume by mouse wheel.

当前为 2022-11-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Volume Mouse Controller
  3. // @namespace wddd
  4. // @version 1.5.0
  5. // @author wddd
  6. // @license MIT
  7. // @description Control YouTube video volume by mouse wheel.
  8. // @homepage https://github.com/wdwind/YouTubeVolumeMouseController
  9. // @match *://www.youtube.com/*
  10. // ==/UserScript==
  11.  
  12. function getPlayerElement() {
  13. var ytd_player = document.getElementsByTagName("ytd-player");
  14. for (var player of ytd_player) {
  15. if (player.getPlayer()) {
  16. return player;
  17. }
  18. }
  19. }
  20.  
  21. function getVideo() {
  22. return getPlayerElement().getElementsByTagName("video")[0];
  23. }
  24.  
  25. function getPlayer() {
  26. return getPlayerElement().getPlayer();
  27. }
  28.  
  29. function run() {
  30. var player = getPlayer();
  31.  
  32. if (!player) {
  33. // eslint-disable-next-line no-console
  34. console.log("Player not found (yet).");
  35. return;
  36. }
  37.  
  38. var timer = 0;
  39.  
  40. // detect available wheel event
  41. var support = "onwheel" in document.createElement("div") ? "wheel" : // Modern browsers support "wheel"
  42. document.onmousewheel !== undefined ? "mousewheel" : // Webkit and IE support at least "mousewheel"
  43. "DOMMouseScroll"; // let"s assume that remaining browsers are older Firefox
  44.  
  45. getVideo().addEventListener(support, function (event) {
  46. var volume = player.getVolume();
  47. var volumeDelta = 5;
  48. var deltaY = support == "mousewheel" ? event.wheelDelta : (event.deltaY || event.detail);
  49. // Optimize volume change for touchpad
  50. if (Math.abs(deltaY) < 5) {
  51. volumeDelta = Math.max(Math.floor(Math.abs(deltaY)), 1);
  52. }
  53.  
  54. volume += (deltaY > 0 ? -volumeDelta : volumeDelta);
  55.  
  56. // Limit the volume between 0 and 100
  57. volume = Math.max(0, Math.min(100, volume));
  58.  
  59. if (volume > 0) {
  60. player.unMute(true);
  61. }
  62. player.setVolume(volume, true);
  63.  
  64. timer = showSlider(timer, volume);
  65.  
  66. // Prevent the page to scroll
  67. event.preventDefault();
  68. event.stopImmediatePropagation();
  69. return false;
  70. });
  71. }
  72.  
  73. function showSlider(timer, volume) {
  74. if (timer) {
  75. clearTimeout(timer);
  76. }
  77.  
  78. var sliderBar = appendSlideBar();
  79.  
  80. sliderBar.style.display = "block";
  81. timer = setTimeout(function () {
  82. sliderBar.style.display = "none";
  83. }, 1000);
  84.  
  85. sliderBar.innerText = "Volume: " + volume;
  86.  
  87. return timer;
  88. }
  89.  
  90. function appendSlideBar() {
  91. var sliderBar = document.getElementById("sliderBar");
  92. if (!sliderBar) {
  93. var sliderBarElement = document.createElement("div");
  94. sliderBarElement.id = "sliderBar";
  95.  
  96. getPlayerElement().getElementsByClassName("html5-video-container")[0].appendChild(sliderBarElement);
  97.  
  98. sliderBar = document.getElementById("sliderBar");
  99. addCss(sliderBar, {
  100. "width": "100%",
  101. "height": "20px",
  102. "position": "relative",
  103. "z-index": "9999",
  104. "text-align": "center",
  105. "color": "#fff",
  106. "font-size": "initial",
  107. "opacity": "0.9",
  108. "background-color": "rgba(0,0,0,0.2)",
  109. });
  110. }
  111.  
  112. addCss(sliderBar, {"top": getSliderBarTopProp() + "px"});
  113.  
  114. return sliderBar;
  115. }
  116.  
  117. function addCss(element, css) {
  118. for (var cssAttr in css) {
  119. element.style[cssAttr] = css[cssAttr];
  120. }
  121. }
  122.  
  123. function getSliderBarTopProp() {
  124. var fullScreenTitleHeight = 0;
  125.  
  126. var fullScreenTitle = document.getElementsByClassName("ytp-title")[0];
  127. if (fullScreenTitle && fullScreenTitle.offsetParent) {
  128. fullScreenTitleHeight = fullScreenTitle.offsetHeight;
  129. }
  130.  
  131. var videoTop = getVideo().getBoundingClientRect().top;
  132. var headerBoundingBox =
  133. (document.getElementById("masthead-positioner") || document.getElementById("masthead-container")).getBoundingClientRect();
  134. var headerTop = headerBoundingBox.top;
  135. var headerHeight = headerBoundingBox.height;
  136.  
  137. var overlap = (headerHeight + headerTop > 0) ? Math.max(0, headerHeight - videoTop) : 0;
  138.  
  139. return Math.max(fullScreenTitleHeight, overlap);
  140. }
  141.  
  142. /**
  143. * YouTube use Javascript to navigate between pages. So the script will not work:
  144. * 1. If the script only includes/matches the sub pages (like the video page www.youtube.com/watch?v=...)
  145. * 2. And the user navigates to the sub page from a page which is not included/matched by the script
  146. *
  147. * In the above scenario, the script will not be executed.
  148. *
  149. * To run the script in all cases,
  150. * 1. Include/match the whole YouTube host
  151. * 2. Detect Javascript events, and run the script appropriately
  152. *
  153. * Details:
  154. * * https://stackoverflow.com/questions/32275387/recall-tampermonkey-script-when-page-location-changes/32277150#32277150
  155. * * https://stackoverflow.com/questions/34077641/how-to-detect-page-navigation-on-youtube-and-modify-html-before-page-is-rendered
  156. * * https://github.com/1c7/Youtube-Auto-Subtitle-Download/blob/master/Youtube-Subtitle-Downloader/Tampermonkey.js#L122-L152
  157. */
  158.  
  159. // trigger when navigating to new material design page
  160. window.addEventListener("yt-navigate-finish", function () {
  161. if (window.location.href.includes("/watch?v=")) {
  162. run();
  163. }
  164. });
  165.  
  166. // trigger when navigating to the old page
  167. window.addEventListener("spfdone", function () {
  168. if (window.location.href.includes("/watch?v=")) {
  169. run();
  170. }
  171. });
  172.  
  173. // trigger when directly loading the page
  174. window.addEventListener("DOMContentLoaded", function () {
  175. if (window.location.href.includes("/watch?v=")) {
  176. run();
  177. }
  178. });
  179.  
  180. /**
  181. * Use MutationObserver to cover all edge cases.
  182. * https://stackoverflow.com/a/39803618
  183. *
  184. * This is to handle the use case where navigation happens but <video> has not been loaded yet.
  185. * (In YouTube the contents are loaded asynchronously.)
  186. */
  187. var observer = new MutationObserver(function() {
  188. if (getVideo() && getPlayer()) {
  189. observer.disconnect();
  190. run();
  191. }
  192. });
  193.  
  194. observer.observe(document.body, /* config */ {childList: true, subtree: true});

QingJ © 2025

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