YouTube Volume Mouse Controller

Control YouTube video volume by mouse wheel.

当前为 2022-05-16 提交的版本,查看 最新版本

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