// ==UserScript== // @name 音量增强器 // @name:en-US Volume Booster // @namespace System233 // @version 0.1 // @description 增强页面音视频音量 // @description:en-US Increase page audio and video volume // @author System233 // @match *://*/* // @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com // @grant none // @license GPL-3.0-only // @downloadURL none // ==/UserScript== // Copyright (c) 2022 System233 // // This software is released under the GPL-3.0 License. // https://opensource.org/licenses/GPL-3.0 (function () { 'use strict'; const setup = () => { // /** @type {('Control'|'Shift'|'Alt')[]} */ const hotKeys = { Control: true, Shift: false, Alt: true, }; /** @type {(HTMLVideoElement|HTMLAudioElement)[]} */ const nodes = [...document.querySelectorAll('video,audio')]; nodes.forEach(node => { const audioCtx = new AudioContext(); const gainNode = audioCtx.createGain(); const mediaSource = audioCtx.createMediaElementSource(node); gainNode.connect(audioCtx.destination); mediaSource.connect(gainNode); let volume = 1; Object.defineProperty(node, 'volumeBoost', { get() { return volume; }, set(value) { volume = Math.max(value,0); gainNode.gain.setValueAtTime(volume, audioCtx.currentTime); } }) }); let index = 0; let active = false; let cleanupHandler = null; const current = { Control: false, Shift: false, Alt: false, } const length = nodes.length; const cleanup = () => { if (cleanupHandler != null) { cleanupHandler(); cleanupHandler = null; } } const keys = Object.keys(current); const check = () => keys.findIndex(x => current[x] != hotKeys[x]) == -1; const updateSelector = () => { active = check(); cleanup(); if (active) { focusOn(index); updateText(true); }else{ updateText(false); } } const focusOn = (direction) => { index = (index + length + direction) % length; const node = nodes[index]; const { border, boxSizing } = node.style; node.style.border = '.5em solid red'; node.style.boxSizing = 'border-box'; const rect = node.getBoundingClientRect(); if (rect.top + rect.height > window.innerHeight || rect.top < 0 || rect.left < 0 || rect.left + rect.width > window.innerWidth) { node.scrollIntoView(); } cleanupHandler = () => { node.style.border = border; node.style.boxSizing = boxSizing; }; } const updateText=(show)=>{ const META_KEY='VolumeBooster'; const node = nodes[index]; if(!show){ const el=Reflect.get(node,META_KEY); if(el){ el.remove(); } return; } /** @type {HTMLDivElement} */ const volumeBooster=Reflect.get(node,META_KEY)||document.createElement('div'); volumeBooster.style.color='red'; volumeBooster.style.position= 'absolute'; volumeBooster.style.right=0; volumeBooster.style.background= '#ccc'; volumeBooster.style.padding='0.2em'; volumeBooster.style.zIndex=10000; volumeBooster.innerHTML=`${Math.round(node.volumeBoost*100)}%`; if(!volumeBooster.parentNode){ node.parentNode.append(volumeBooster); Reflect.set(node,META_KEY,volumeBooster); } } const boost = (step) => { const node = nodes[index]; node.volumeBoost += step; updateText(true); } const press = (key, press) => { if (key in current) { current[key] = press } updateSelector(); } if(length){ document.addEventListener('keyup', e => press(e.key, false)); document.addEventListener('keydown', e => press(e.key, true)); document.addEventListener('keydown', e => { if (active) { switch (e.key) { case 'ArrowUp': boost(.1); break; case 'ArrowDown': boost(-.1); break; case 'ArrowLeft': focusOn(-1); break; case 'ArrowRight': focusOn(1); break; } } }) } } window.addEventListener('load', setup); })();