YouTube Volume Mouse Controller

Control YouTube video volume by mouse wheel.

目前为 2019-02-05 提交的版本。查看 最新版本

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