YouTube Volume Mouse Controller

Control YouTube video volume by mouse wheel.

当前为 2024-08-14 提交的版本,查看 最新版本

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

QingJ © 2025

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