YouTube Volume Mouse Controller

Control YouTube video volume by mouse wheel.

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

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