YouTube Volume Control with Memory

Set YouTube volume manually on a scale of 1-100, remember last set volume, and inject the UI to the left of the volume slider on the video player. Syncs the slider, disables invalid inputs, and adds debugging.

当前为 2025-01-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Volume Control with Memory
  3. // @namespace typpi.online
  4. // @version 4.1
  5. // @description Set YouTube volume manually on a scale of 1-100, remember last set volume, and inject the UI to the left of the volume slider on the video player. Syncs the slider, disables invalid inputs, and adds debugging.
  6. // @author Nick2bad4u
  7. // @match *://www.youtube.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  9. // @grant GM.getValue
  10. // @grant GM.setValue
  11. // @license UnLicense
  12. // @tag youtube
  13. // ==/UserScript==
  14.  
  15. (async function () {
  16. 'use strict';
  17.  
  18. // Default volume if none is saved
  19. let previousVolume = await GM.getValue(
  20. 'youtubeVolume',
  21. 5,
  22. );
  23.  
  24. // Create input element for volume control
  25. const volumeInput =
  26. document.createElement('input');
  27. volumeInput.type = 'number';
  28. volumeInput.min = 0;
  29. volumeInput.max = 100;
  30. volumeInput.value = previousVolume;
  31.  
  32. // Set input field styles to resemble YouTube's UI
  33. volumeInput.style.width = '30px';
  34. volumeInput.style.marginRight = '10px';
  35. volumeInput.style.backgroundColor =
  36. 'rgba(255, 255, 255, 0.0)';
  37. volumeInput.style.color = 'white';
  38. volumeInput.style.border =
  39. '0px solid rgba(255, 255, 255, 0.0)';
  40. volumeInput.style.borderRadius = '4px';
  41. volumeInput.style.zIndex = 9999;
  42. volumeInput.style.height = '24px';
  43. volumeInput.style.fontSize = '16px';
  44. volumeInput.style.padding = '0 4px';
  45. volumeInput.style.transition =
  46. 'border-color 0.3s, background-color 0.3s';
  47. volumeInput.style.outline = 'none';
  48. volumeInput.style.position = 'relative';
  49. volumeInput.style.top = '13px';
  50.  
  51. // Change border color on focus
  52. volumeInput.addEventListener('focus', () => {
  53. volumeInput.style.borderColor =
  54. 'rgba(255, 255, 255, 0.6)';
  55. });
  56.  
  57. volumeInput.addEventListener('blur', () => {
  58. volumeInput.style.borderColor =
  59. 'rgba(255, 255, 255, 0.3)';
  60. });
  61.  
  62. // Change background color on hover
  63. volumeInput.addEventListener(
  64. 'mouseenter',
  65. () => {
  66. volumeInput.style.backgroundColor =
  67. 'rgba(0, 0, 0, 0.8)';
  68. },
  69. );
  70.  
  71. volumeInput.addEventListener(
  72. 'mouseleave',
  73. () => {
  74. volumeInput.style.backgroundColor =
  75. 'rgba(255, 255, 255, 0.0)';
  76. },
  77. );
  78.  
  79. // Prevent YouTube hotkeys when typing in the input
  80. volumeInput.addEventListener(
  81. 'keydown',
  82. function (event) {
  83. event.stopPropagation();
  84. console.log(
  85. 'Keydown event in volume input, stopping propagation.',
  86. );
  87. },
  88. );
  89.  
  90. // Function to set the volume based on input value
  91. async function setVolume(volumeValue) {
  92. const player =
  93. document.querySelector('video');
  94. if (player) {
  95. // Validate input (must be between 0 and 100)
  96. if (volumeValue < 0) volumeValue = 0;
  97. if (volumeValue > 100) volumeValue = 100;
  98. volumeInput.value = volumeValue;
  99.  
  100. // Set the player volume and save to Tampermonkey storage
  101. player.volume = volumeValue / 100;
  102. await GM.setValue(
  103. 'youtubeVolume',
  104. volumeValue,
  105. );
  106. console.log(
  107. `Volume set to ${volumeValue} and saved to Tampermonkey storage.`,
  108. );
  109.  
  110. // Sync YouTube's volume slider UI
  111. const volumeSlider = document.querySelector(
  112. '.ytp-volume-slider-handle',
  113. );
  114. if (volumeSlider) {
  115. volumeSlider.style.left = `${volumeValue}%`;
  116. console.log(
  117. 'YouTube volume slider updated.',
  118. );
  119. }
  120. }
  121. }
  122.  
  123. // Event listener for input change (manually changing the volume in the input box)
  124. volumeInput.addEventListener('input', () =>
  125. setVolume(volumeInput.value),
  126. );
  127.  
  128. // Function to update the input field when YouTube's player volume is changed
  129. async function updateVolumeInput() {
  130. const player =
  131. document.querySelector('video');
  132. if (player) {
  133. const currentVolume = Math.round(
  134. player.volume * 100,
  135. );
  136. volumeInput.value = currentVolume;
  137. await GM.setValue(
  138. 'youtubeVolume',
  139. currentVolume,
  140. );
  141. console.log(
  142. `Volume input updated to ${currentVolume} from video player.`,
  143. );
  144.  
  145. // Show 0 if the video is muted
  146. if (player.muted) {
  147. volumeInput.value = 0;
  148. }
  149. }
  150. }
  151.  
  152. // Function to handle mute changes
  153. async function handleMuteChange() {
  154. const player =
  155. document.querySelector('video');
  156. if (player) {
  157. if (player.muted) {
  158. volumeInput.value = 0; // Show 0 when muted
  159. } else {
  160. volumeInput.value = previousVolume; // Restore previous volume when unmuted
  161. player.volume = previousVolume / 100; // Set the player volume back to previous
  162. }
  163. console.log(
  164. `Mute state changed: muted = ${player.muted}`,
  165. );
  166. }
  167. }
  168.  
  169. // Inject the input box into YouTube's control bar
  170. function injectVolumeControl() {
  171. const volumeSliderPanel =
  172. document.querySelector('.ytp-volume-panel');
  173. if (volumeSliderPanel) {
  174. volumeSliderPanel.parentNode.insertBefore(
  175. volumeInput,
  176. volumeSliderPanel,
  177. );
  178. setVolume(previousVolume); // Set initial volume
  179. const player =
  180. document.querySelector('video');
  181. if (player) {
  182. player.addEventListener(
  183. 'volumechange',
  184. updateVolumeInput,
  185. );
  186. player.addEventListener(
  187. 'mute',
  188. handleMuteChange,
  189. );
  190. player.addEventListener(
  191. 'unmute',
  192. handleMuteChange,
  193. );
  194. console.log(
  195. 'Volume input injected and event listeners attached.',
  196. );
  197. }
  198. } else {
  199. console.log(
  200. 'Volume panel not found, retrying...',
  201. );
  202. setTimeout(injectVolumeControl, 500);
  203. }
  204. }
  205.  
  206. // Inject the volume control when the page is ready
  207. injectVolumeControl();
  208. })();