YouTube Volume Mouse Controller

Control YouTube video volume by mouse wheel.

目前为 2019-12-01 提交的版本。查看 最新版本

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