Video Volume Booster

增強影片音量上限 , 最高增幅至10倍 , 未測試是否所有網域皆可使用 *://*/* , 目前只match特定網域

当前为 2023-08-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Video Volume Booster
  3. // @version 0.0.24
  4. // @author HentaiSaru
  5. // @license MIT
  6. // @icon https://cdn-icons-png.flaticon.com/512/8298/8298181.png
  7. // @description 增強影片音量上限 , 最高增幅至10倍 , 未測試是否所有網域皆可使用 *://*/* , 目前只match特定網域
  8.  
  9. // @run-at document-start
  10. // @match *://*.twitch.tv/*
  11. // @match *://*.youtube.com/*
  12. // @match *://*.bilibili.com/*
  13.  
  14. // @exclude *://video.eyny.com/*
  15.  
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // @grant GM_addStyle
  19. // @grant GM_registerMenuCommand
  20. // @namespace https://greasyfork.org/users/989635
  21. // ==/UserScript==
  22.  
  23. var Booster, modal, enabledDomains = GM_getValue("啟用網域", []), domain = window.location.hostname, Increase = 1.0;
  24. (function() {
  25. FindVideo();
  26. MenuHotkey();
  27. MonitorAjax();// 暴力解法(多少影響效能 , 個人沒感覺)
  28. GM_registerMenuCommand("🔊 [開關] 自動增幅", function() {Useboost(enabledDomains, domain)});
  29. GM_registerMenuCommand("🛠️ 設置增幅", function() {IncrementalSetting()});
  30. GM_registerMenuCommand("📜 菜單熱鍵", function() {
  31. alert("可使用熱鍵方式呼叫設置菜單!!\n\n快捷組合 : (Alt + B)");
  32. });
  33. })();
  34.  
  35. /* 監聽 Ajex 變化(fetch的不支援) */
  36. async function MonitorAjax() {
  37. let Video, VideoCache;
  38. const originalXHROpen = XMLHttpRequest.prototype.open;
  39. XMLHttpRequest.prototype.open = function () {
  40. this.addEventListener("readystatechange", function () {
  41. Video = document.querySelector("video");
  42. if (this.readyState === 4 && Video) {
  43. if (!Video.hasAttribute("data-audio-context") && Video !== VideoCache) {
  44. FindVideo();
  45. VideoCache = Video;
  46. }
  47. }
  48. });
  49. return originalXHROpen.apply(this, arguments);
  50. }
  51. }
  52.  
  53. /* 搜索 Video 元素 */
  54. async function FindVideo() {
  55. let interval, timeout=0;
  56. interval = setInterval(function() {
  57. const videoElement = document.querySelector("video");
  58. if (videoElement) {
  59. if (enabledDomains.includes(domain)) { // 沒開啟自動增幅的網頁也可以嘗試使用
  60. let inc = GM_getValue(domain, []);
  61. if (inc.length !== 0) {
  62. Increase = inc;
  63. }
  64. }
  65. try {
  66. Booster = booster(videoElement, Increase);
  67. } catch (error) {console.log(error)}
  68. clearInterval(interval);
  69. } else {
  70. timeout++;
  71. if (timeout === 3) {
  72. clearInterval(interval);
  73. }
  74. }
  75. }, 300);
  76. }
  77.  
  78. /* 註冊快捷鍵(開啟菜單) */
  79. async function MenuHotkey() {
  80. document.addEventListener("keydown", function(event) {
  81. if (event.altKey && event.key === "b") {
  82. IncrementalSetting();
  83. }
  84. });
  85. }
  86.  
  87. /* 音量增量 */
  88. function booster(video, increase) {
  89. const audioContext = new (window.AudioContext || window.webkitAudioContext);
  90. // 音頻來源
  91. const source = audioContext.createMediaElementSource(video);
  92. // 增益節點
  93. const gainNode = audioContext.createGain();
  94. // 動態壓縮節點
  95. const compressorNode = audioContext.createDynamicsCompressor();
  96.  
  97. // 將預設音量調整至 100% (有可能被其他腳本調整)
  98. video.volume = 1;
  99. // 設置增量
  100. gainNode.gain.value = increase * increase;
  101.  
  102. // 設置動態壓縮器的參數(通用性測試!!)
  103. compressorNode.ratio.value = 9; // 壓縮率
  104. compressorNode.knee.value = 6; // 壓縮過度反應時間(越小越快)
  105. compressorNode.threshold.value = -9; // 壓縮閾值
  106. compressorNode.attack.value = 0.003; // 開始壓縮的速度
  107. compressorNode.release.value = 0.6; // 釋放壓縮的速度
  108.  
  109. // 進行節點連結
  110. source.connect(gainNode);
  111. gainNode.connect(compressorNode);
  112. compressorNode.connect(audioContext.destination);
  113. video.setAttribute("data-audio-context", true);
  114. return {
  115. // 設置音量
  116. setVolume: function(increase) {
  117. gainNode.gain.value = increase * increase;
  118. Increase = increase;
  119. }
  120. }
  121. }
  122.  
  123. /* 使用自動增幅 */
  124. function Useboost(enabledDomains, domain) {
  125. if (enabledDomains.includes(domain)) {
  126. // 從已啟用列表中移除當前網域
  127. enabledDomains = enabledDomains.filter(function(value) {
  128. return value !== domain;
  129. });
  130. alert("❌ 禁用自動增幅");
  131. } else {
  132. // 添加當前網域到已啟用列表
  133. enabledDomains.push(domain);
  134. alert("✅ 啟用自動增幅");
  135. }
  136. GM_setValue("啟用網域", enabledDomains);
  137. location.reload();
  138. }
  139.  
  140. GM_addStyle(`
  141. .YT-modal-background {
  142. top: 0;
  143. left: 0;
  144. width: 100%;
  145. height: 100%;
  146. z-index: 9999;
  147. display: flex;
  148. position: fixed;
  149. overflow: auto;
  150. justify-content: center;
  151. align-items: center;
  152. background-color: rgba(0, 0, 0, 0.1);
  153. }
  154. .YT-modal-button {
  155. top: 0;
  156. margin: 3% 2%;
  157. color: #d877ff;
  158. font-size: 16px;
  159. font-weight: bold;
  160. border-radius: 3px;
  161. background-color: #ffebfa;
  162. border: 1px solid rgb(124, 183, 252);
  163. }
  164. .YT-modal-button:hover,
  165. .YT-modal-button:focus {
  166. color: #fc0e85;
  167. cursor: pointer;
  168. text-decoration: none;
  169. }
  170. .YT-modal-content {
  171. width: 400px;
  172. padding: 5px;
  173. overflow: auto;
  174. background-color: #cff4ff;
  175. border-radius: 10px;
  176. text-align: center;
  177. border: 2px ridge #82c4e2;
  178. border-collapse: collapse;
  179. margin: 2% auto 8px auto;
  180. }
  181. .multiplier {
  182. font-size:25px;
  183. color:rgb(253, 1, 85);
  184. margin: 10px;
  185. font-weight:bold;
  186. }
  187. .slider {
  188. width: 350px;
  189. }
  190. .hidden {
  191. display: none;
  192. }
  193. `);
  194.  
  195. /* 設定模態 */
  196. function IncrementalSetting() {
  197. if (modal) {
  198. modal.remove();
  199. modal = null;
  200. }
  201.  
  202. modal = document.createElement('div');
  203. modal.innerHTML = `
  204. <div class="YT-modal-content">
  205. <h2 style="color: #3754f8;">音量增量</h2>
  206. <div style="margin:1rem auto 1rem auto;">
  207. <div class="multiplier">
  208. <span><img src="https://cdn-icons-png.flaticon.com/512/8298/8298181.png" width="5%">增量倍數 </span><span id="currentValue">${Increase}</span><span> 倍</span>
  209. </div>
  210. <input type="range" class="slider" min="0" max="10.0" value="${Increase}" step="0.1"><br>
  211. </div>
  212. <div style="text-align: right;">
  213. <button class="YT-modal-button" id="save">保存設置</button>
  214. <button class="YT-modal-button" id="close">退出選單</button>
  215. </div>
  216. </div>
  217. `
  218. modal.classList.add('YT-modal-background');
  219. document.body.appendChild(modal);
  220. modal.classList.remove('hidden');
  221.  
  222. // 監聽設定拉條
  223. modal.addEventListener("input", function(event) {
  224. if (event.target.classList.contains("slider")) {
  225. let currentValueElement = document.getElementById("currentValue");
  226. let currentValue = event.target.value;
  227. currentValueElement.textContent = currentValue;
  228. Booster.setVolume(currentValue);
  229. }
  230. });
  231.  
  232. // 監聽保存按鈕
  233. let saveButton = modal.querySelector("#save");
  234. saveButton.addEventListener("click", () => {
  235. if (enabledDomains.includes(domain)) {
  236. let rangeValue = parseFloat(modal.querySelector(".slider").value);
  237. GM_setValue(domain, rangeValue);
  238. modal.classList.add("hidden");
  239. } else {
  240. alert("需啟用自動增幅才可保存");
  241. }
  242. });
  243.  
  244. // 監聽關閉按鈕點擊
  245. let CloseButton = modal.querySelector("#close");
  246. CloseButton.addEventListener("click", () => {
  247. modal.classList.add("hidden");
  248. });
  249. }