YouTube Volume Mouse Controller

Control YouTube video volume by mouse wheel.

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

QingJ © 2025

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