// ==UserScript== // @name Media Volume Booster // @name:zh-TW 媒體音量增強器 // @name:zh-CN 媒体音量增强器 // @name:en Media Volume Booster // @version 2025.08.08-Beta // @author Canaan HS // @description 調整媒體音量與濾波器,增強倍數最高 20 倍,設置可記住並自動應用。部分網站可能無效、無聲音或無法播放,可選擇禁用。 // @description:zh-TW 調整媒體音量與濾波器,增強倍數最高 20 倍,設置可記住並自動應用。部分網站可能無效、無聲音或無法播放,可選擇禁用。 // @description:zh-CN 调整媒体音量与滤波器,增强倍数最高 20 倍,设置可记住并自动应用。部分网站可能无效、无声音或无法播放,可选择禁用。 // @description:en Adjust media volume and filters with enhancement factor up to 20x. Settings are saved and auto-applied. May not work on some sites (causing no sound or playback issues). Can be disabled if needed. // @noframes // @match *://*/* // @icon https://cdn-icons-png.flaticon.com/512/16108/16108408.png // @license MPL-2.0 // @namespace https://greasyfork.org/users/989635 // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_getResourceURL // @grant GM_registerMenuCommand // @grant GM_addValueChangeListener // @resource Img https://cdn-icons-png.flaticon.com/512/11243/11243783.png // @require https://update.greasyfork.icu/scripts/487608/1637584/SyntaxLite_min.js // @run-at document-body // @downloadURL none // ==/UserScript== (function() { const Share = { Parame: null, SetControl: null }; const Default = { Gain: 1, LowFilterGain: 1.2, LowFilterFreq: 200, MidFilterQ: 1, MidFilterGain: 1.6, MidFilterFreq: 2e3, HighFilterGain: 1.8, HighFilterFreq: 1e4, CompressorRatio: 3, CompressorKnee: 4, CompressorThreshold: -8, CompressorAttack: .03, CompressorRelease: .2 }; function CreateMenu(Lib2, Share2, Img, Transl2) { return async () => { const shadowID = "Booster_Menu"; if (Lib2.$q(`#${shadowID}`)) return; const shadow = Lib2.createElement(Lib2.body, "div", { id: shadowID }); const shadowRoot = shadow.attachShadow({ mode: "open" }); const style = ` `; const generateOtherTemplate = (label, groups) => `
${groups.map(group => `
${Transl2(group.label)} ${Share2.Parame[group.id]}
`).join("")}
`; shadowRoot.$iHtml(` ${style} <${shadowID} id="Booster-Modal-Menu">

${Transl2("音量增強器")}

${Transl2("增強倍數 ")} ${Share2.Parame.Gain}${Transl2(" 倍")}
${generateOtherTemplate("低頻設定", [ { label: "增益", id: "LowFilterGain", min: "-12", max: "12", step: "0.1" }, { label: "頻率", id: "LowFilterFreq", min: "20", max: "1000", step: "20" } ])} ${generateOtherTemplate("中頻設定", [ { label: "增益", id: "MidFilterGain", min: "-12", max: "12", step: "0.1" }, { label: "頻率", id: "MidFilterFreq", min: "200", max: "8000", step: "100" }, { label: "Q值", id: "MidFilterQ", min: "0.5", max: "5", step: "0.1" } ])} ${generateOtherTemplate("高頻設定", [ { label: "增益", id: "HighFilterGain", min: "-12", max: "12", step: "0.1" }, { label: "頻率", id: "HighFilterFreq", min: "2000", max: "22000", step: "500" } ])} ${generateOtherTemplate("動態壓縮", [ { label: "壓縮率", id: "CompressorRatio", min: "1", max: "30", step: "0.1" }, { label: "過渡反應", id: "CompressorKnee", min: "0", max: "40", step: "1" }, { label: "閾值", id: "CompressorThreshold", min: "-60", max: "0", step: "1" }, { label: "起音速度", id: "CompressorAttack", min: "0.001", max: "0.5", step: "0.001" }, { label: "釋放速度", id: "CompressorRelease", min: "0.01", max: "2", step: "0.01" } ])}
`); const shadowGate = shadow.shadowRoot; const modal = shadowGate.querySelector(shadowID); const content = shadowGate.querySelector(".Booster-Modal-Content"); function deleteMenu() { modal.classList.add("close"); content.classList.add("close"); setTimeout(() => { shadow.remove(); }, 800); } const displayMap = { ...Object.fromEntries([ ...shadowGate.querySelectorAll(".Booster-Label") ].map(el => [ el.id, el ])) }; function updateControl(id, value) { displayMap[`${id}-Label`].textContent = value; shadowGate.querySelector(`#${id}`).value = value; Share2.SetControl(id, value); } content.addEventListener("input", event => { const target = event.target; if (target.type !== "range") return; const id = target.id; const value = parseFloat(target.value); updateControl(id, value); }); content.addEventListener("click", event => { const target = event.target; if (!target.classList.contains("Booster-Label") || target.isEditing) return; target.isEditing = true; const originalValue = target.textContent.trim(); const controlId = target.id.replace("-Label", ""); const slider = shadowGate.querySelector(`#${controlId}`); const input = Lib2.createElement("input", { class: "Booster-Label-Input", value: originalValue, on: [ { type: "blur", listener: () => { let newValue = parseFloat(input.value); const min = parseFloat(slider.min); const max = parseFloat(slider.max); if (isNaN(newValue)) { newValue = parseFloat(originalValue); } else if (newValue < min) { newValue = min; } else if (newValue > max) { newValue = max; } target.isEditing = false; updateControl(controlId, newValue); target.textContent = newValue; }, add: { once: true } }, { type: "keydown", listener: e => { if (e.key === "Enter") e.target.blur(); if (e.key === "Escape") { e.target.value = originalValue; e.target.blur(); } } } ] }); target.textContent = ""; target.appendChild(input); input.focus(); }); modal.addEventListener("click", click => { const target = click.target; click.stopPropagation(); if (target.classList.contains("Booster-Accordion")) { target.classList.toggle("active"); const panel = target.nextElementSibling; if (panel.style.maxHeight) { panel.style.maxHeight = null; panel.classList.remove("active"); } else { panel.style.maxHeight = panel.scrollHeight + "px"; panel.classList.add("active"); } } else if (target.id === "Booster-Sound-Save") { Lib2.setV(Lib2.domain, Share2.Parame); deleteMenu(); } else if (target.id === "Booster-Menu-Close" || target.id === "Booster-Modal-Menu") { deleteMenu(); } }); }; } const Words = { Traditional: {}, Simplified: { "🛠️ 調整菜單": "🛠️ 调整菜单", "✂️ 斷開增幅": "✂️ 断开增幅", "🔗 恢復增幅": "🔗 恢复增幅", "❌ 禁用網域": "❌ 禁用网域", "✅ 啟用網域": "✅ 启用网域", "增強錯誤": "增强错误", "音量增強器": "音量增强器", "增強倍數 ": "增强倍数 ", " 倍": " 倍", "增益": "增益", "頻率": "频率", "Q值": "Q值", "低頻設定": "低频设置", "中頻設定": "中频设置", "高頻設定": "高频设置", "動態壓縮": "动态压缩", "壓縮率": "压缩率", "過渡反應": "过渡反应", "閾值": "阈值", "起音速度": "起音速度", "釋放速度": "释放速度", "關閉": "关闭", "保存": "保存", "不支援的媒體跳過": "不支持的媒体跳过", "不支援音頻增強節點": "不支持音频增强节点", "添加增強節點成功": "添加增强节点成功", "添加增強節點失敗": "添加增强节点失败", "當前沒有被增幅的媒體": "当前没有被增幅的媒体", "快捷組合 : (Alt + B)": "快捷组合 : (Alt + B)" }, English: { "🛠️ 調整菜單": "🛠️ Settings Menu", "✂️ 斷開增幅": "✂️ Disconnect Amplification", "🔗 恢復增幅": "🔗 Restore Amplification", "❌ 禁用網域": "❌ Disable Domain", "✅ 啟用網域": "✅ Enable Domain", "增強錯誤": "Enhancement Error", "音量增強器": "Volume Booster", "增強倍數 ": "Enhancement Multiplier ", " 倍": "x", "增益": "Gain", "頻率": "Frequency", "Q值": "Q Factor", "低頻設定": "Low Frequency Settings", "中頻設定": "Mid Frequency Settings", "高頻設定": "High Frequency Settings", "動態壓縮": "Dynamic Compressor", "壓縮率": "Compression Ratio", "過渡反應": "Knee", "閾值": "Threshold", "起音速度": "Attack", "釋放速度": "Release", "關閉": "Close", "保存": "Save", "不支援的媒體跳過": "Unsupported Media Skipped", "不支援音頻增強節點": "Audio Enhancement Node Not Supported", "添加增強節點成功": "Enhancement Node Added Successfully", "添加增強節點失敗": "Failed to Add Enhancement Node", "當前沒有被增幅的媒體": "No media is currently being amplified", "快捷組合 : (Alt + B)": "Shortcut: (Alt + B)" } }; const bannedDomains = (() => { let banned = new Set(Lib.getV("Banned", [])); let excludeStatus = banned.has(Lib.$domain); return { isEnabled: callback => callback(!excludeStatus), addBanned: async () => { excludeStatus ? banned.delete(Lib.$domain) : banned.add(Lib.$domain); Lib.setV("Banned", [ ...banned ]); location.reload(); } }; })(); const { Transl } = (() => { const matcher = Lib.translMatcher(Words); return { Transl: Str => matcher[Str] ?? Str }; })(); (async () => { let menu = null; let updated = false; let processing = false; let initialized = false; const enhancedNodes = []; const processedElements = new Map(); let mediaAudioContent = null; const audioContext = window.AudioContext || window.webkitAudioContext; const updateParame = () => { let Config = Lib.getV(Lib.$domain, {}); if (typeof Config === "number") { Config = { Gain: Config }; } Share.Parame = Object.assign(Default, Config); }; function boosterCore(mediaObject) { try { if (!audioContext) throw new Error(Transl("不支援音頻增強節點")); if (!mediaAudioContent) mediaAudioContent = new audioContext(); if (mediaAudioContent.state === "suspended") mediaAudioContent.resume(); const successNode = []; for (const media of mediaObject) { processedElements.set(media, true); if (media.mediaKeys || media.encrypted || window.MediaSource && media.srcObject instanceof MediaSource) { Lib.log(Transl("不支援的媒體跳過"), media, { collapsed: false }); continue; } try { if (!media.crossOrigin) media.crossOrigin = "anonymous"; const SourceNode = mediaAudioContent.createMediaElementSource(media); const GainNode = mediaAudioContent.createGain(); const LowFilterNode = mediaAudioContent.createBiquadFilter(); const MidFilterNode = mediaAudioContent.createBiquadFilter(); const HighFilterNode = mediaAudioContent.createBiquadFilter(); const CompressorNode = mediaAudioContent.createDynamicsCompressor(); GainNode.gain.value = Share.Parame.Gain; LowFilterNode.type = "lowshelf"; LowFilterNode.gain.value = Share.Parame.LowFilterGain; LowFilterNode.frequency.value = Share.Parame.LowFilterFreq; MidFilterNode.type = "peaking"; MidFilterNode.Q.value = Share.Parame.MidFilterQ; MidFilterNode.gain.value = Share.Parame.MidFilterGain; MidFilterNode.frequency.value = Share.Parame.MidFilterFreq; HighFilterNode.type = "highshelf"; HighFilterNode.gain.value = Share.Parame.HighFilterGain; HighFilterNode.frequency.value = Share.Parame.HighFilterFreq; CompressorNode.ratio.value = Share.Parame.CompressorRatio; CompressorNode.knee.value = Share.Parame.CompressorKnee; CompressorNode.threshold.value = Share.Parame.CompressorThreshold; CompressorNode.attack.value = Share.Parame.CompressorAttack; CompressorNode.release.value = Share.Parame.CompressorRelease; SourceNode.connect(GainNode).connect(LowFilterNode).connect(MidFilterNode).connect(HighFilterNode).connect(CompressorNode).connect(mediaAudioContent.destination); enhancedNodes.push({ Destination: mediaAudioContent.destination, SourceNode: SourceNode, GainNode: GainNode, LowFilterNode: LowFilterNode, MidFilterNode: MidFilterNode, HighFilterNode: HighFilterNode, CompressorNode: CompressorNode, Gain: GainNode.gain, LowFilterGain: LowFilterNode.gain, LowFilterFreq: LowFilterNode.frequency, MidFilterQ: MidFilterNode.Q, MidFilterGain: MidFilterNode.gain, MidFilterFreq: MidFilterNode.frequency, HighFilterGain: HighFilterNode.gain, HighFilterFreq: HighFilterNode.frequency, CompressorRatio: CompressorNode.ratio, CompressorKnee: CompressorNode.knee, CompressorThreshold: CompressorNode.threshold, CompressorAttack: CompressorNode.attack, CompressorRelease: CompressorNode.release }); successNode.push(media); } catch (e) { Lib.log(Transl("添加增強節點失敗"), media, { collapsed: false }); } } if (successNode.length > 0) { processing = false; Lib.log(Transl("添加增強節點成功"), successNode, { collapsed: false }); if (!initialized) { let regChange2 = function() { Lib.regMenu({ [Transl(disconnected ? "🔗 恢復增幅" : "✂️ 斷開增幅")]: () => { if (enhancedNodes.length === 0) { alert(Transl("當前沒有被增幅的媒體")); return; } enhancedNodes.forEach(items => { const { SourceNode, GainNode, LowFilterNode, MidFilterNode, HighFilterNode, CompressorNode, Destination } = items; if (disconnected) { SourceNode.connect(GainNode).connect(LowFilterNode).connect(MidFilterNode).connect(HighFilterNode).connect(CompressorNode).connect(Destination); } else { SourceNode.disconnect(); GainNode.disconnect(); LowFilterNode.disconnect(); MidFilterNode.disconnect(); HighFilterNode.disconnect(); CompressorNode.disconnect(); SourceNode.connect(Destination); } }); disconnected = !disconnected; regChange2(); }, [Transl("🛠️ 調整菜單")]: { desc: Transl("快捷組合 : (Alt + B)"), func: () => { menu(); } } }, { index: 2 }); }; var regChange = regChange2; initialized = true; let disconnected = false; regChange2(); Lib.onEvent(document, "keydown", event => { if (event.altKey && event.key.toUpperCase() == "B") menu(); }, { passive: true, capture: true, mark: "Media-Booster-Hotkey" }); Lib.storeListen([ Lib.$domain ], call => { if (call.far && call.key === Lib.$domain) { Object.entries(call.nv).forEach(([ type, value ]) => { Share.SetControl(type, value); }); } }); } } } catch (error) { Lib.log(Transl("增強錯誤"), error, { type: "error", collapsed: false }); } } function trigger(media) { try { if (!updated) { updated = true; updateParame(); } boosterCore(media); } catch (error) { Lib.log("Trigger Error : ", error, { type: "error", collapsed: false }); } } bannedDomains.isEnabled(Status => { const regMenu = async name => { Lib.regMenu({ [name]: () => bannedDomains.addBanned() }); }; if (Status) { Share.SetControl = (type, value) => { Share.Parame[type] = value; enhancedNodes.forEach(items => { items[type].value = value; }); }; menu = CreateMenu(Lib, Share, GM_getResourceURL("Img"), Transl); const findMedia = Lib.$debounce(func => { const media = []; const tree = document.createTreeWalker(Lib.body, NodeFilter.SHOW_ELEMENT, { acceptNode: node => { const tag = node.tagName; if (tag === "VIDEO" || tag === "AUDIO") { if (!processedElements.has(node)) return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_SKIP; } }); while (tree.nextNode()) { media.push(tree.currentNode); } media.length > 0 && func(media); }, 50); Lib.$observer(Lib.body, () => { if (processing) return; findMedia(media => { processing = true; trigger(media); }); }, { mark: "Media-Booster", attributes: false, throttle: 200 }, () => { regMenu(Transl("❌ 禁用網域")); }); Lib.onUrlChange(() => { processedElements.clear(); }); } else regMenu(Transl("✅ 啟用網域")); }); })(); })();