增强页面音视频音量
// ==UserScript== // @name 音量增强器 // @name:en_US Volume Booster // @name:zh-CN 音量增强器 // @namespace System233 // @version 0.3 // @description 增强页面音视频音量 // @description:zh-CN 增强页面音视频音量 // @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 // ==/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 = () => { // 热键配置:当前为CTRL+Shift const hotKeys = { Control: true, Shift: true, Alt: false, }; /** @type {HTMLElement} */ let currentNode = null; /** @type {(node:HTMLElement)=>void} */ const setupNode = node => { if(!node){ return; } if ('volumeBoost' in node) { return; } 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); } }) } const META_CLEANUP = 'VolumeBoosterCleanup'; const current = { Control: false, Shift: false, Alt: false, } const keys = Object.keys(current); const cleanup = (node) => { if (node && Reflect.has(node, META_CLEANUP)) { Reflect.get(node, META_CLEANUP).call(); } } const check = () => keys.findIndex(x => current[x] != hotKeys[x]) == -1; let lastNode=null; const selectNode = (node) => { setupNode(node); cleanup(lastNode); if (!node) { return; } lastNode = node; 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(); } const META_KEY = 'VolumeBooster'; /** @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.top = 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); } Reflect.set(node, META_CLEANUP, () => { node.style.border = border; node.style.boxSizing = boxSizing; volumeBooster.remove(); }); } const boost = (step) => { if (currentNode) { currentNode.volumeBoost += step; selectNode(currentNode); } } const moveNextNode = (next) => { const nodes = Array.from(document.querySelectorAll('video,audio')); if (!nodes.length) { return; } const index = Math.max(currentNode ? nodes.findIndex(node => node == currentNode) : 0, 0); const nextIndex = (index + next + nodes.length) % nodes.length; currentNode=nodes[nextIndex]; } /** @type {(HTMLVideoElement|HTMLAudioElement)[]} */ let active = false; const press = (key, press) => { if (key in current) { current[key] = press } active = check(); } document.addEventListener('keyup', e => press(e.key, false)); document.addEventListener('keydown', e => press(e.key, true)); document.addEventListener('keyup', e => !active&&selectNode(null)); document.addEventListener('keydown', e => { if (active) { if(!currentNode){ moveNextNode(0); } switch (e.key) { case 'ArrowUp': boost(.1); break; case 'ArrowDown': boost(-.1); break; case 'ArrowLeft': moveNextNode(-1); break; case 'ArrowRight': moveNextNode(1); break; } selectNode(currentNode); } }) } window.addEventListener('load', setup); })();