// ==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) => `
${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"
} ])}
${shadowID}>
`);
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("✅ 啟用網域"));
});
})();
})();