- 楼主博主
默认 60% 音量

代码
// ==UserScript== // @name 烧饼娱乐游戏音量调节 // @namespace https://sb.sb/ // @version 1.0.0 // @description 点击音量按钮弹出音量调节面板 // @author Antigravity // @match *://sb.sb/games/* // @match *://sb.sb/games* // @include *://sb.sb/games/* // @include *://sb.sb/games* // @run-at document-start // @grant none // ==/UserScript== (function () { 'use strict'; // --- 1. 配置与状态初始化 --- const STORAGE_KEY_VOLUME = 'bbs-game-master-volume'; const STORAGE_KEY_MUTE = 'bbs-wheel-mute'; const MUTE_BTN_SELECTOR = 'button[data-ddz-mute], button[data-wheel-mute], button[data-slot-mute], button.wheel-mute, button.slot-mute, [data-mute]'; const CONTAINER_SELECTOR = '.wheel-panel, [data-wheel-panel], .form-panel, [data-ddz], .ddz-panel'; // 读取本地存储音量,缺省为 60% let currentVolume = parseInt(localStorage.getItem(STORAGE_KEY_VOLUME) ?? '60', 10); if (isNaN(currentVolume) || currentVolume < 0 || currentVolume > 100) { currentVolume = 60; } // --- 2. 音频总线劫持与控制 --- const ctxMasterGains = new WeakMap(); const allMasterGainNodes = new Set(); const activeMediaElements = new Set(); const origConnect = AudioNode.prototype.connect; // 为每个 AudioContext 获取或创建独立的主增益节点 function getOrCreateMasterGain(ctx) { if (!ctx || !ctx.destination) return null; let master = ctxMasterGains.get(ctx); if (!master) { try { master = ctx.createGain(); const isMuted = localStorage.getItem(STORAGE_KEY_MUTE) === '1'; master.gain.value = isMuted ? 0 : (currentVolume / 100); origConnect.call(master, ctx.destination); ctxMasterGains.set(ctx, master); allMasterGainNodes.add(master); } catch (e) { return null; } } return master; } // 拦截 Web Audio 目标连接,统一经由 MasterGainNode 控制输出增益 AudioNode.prototype.connect = function (destination, ...args) { try { if (this.context && destination === this.context.destination) { const master = getOrCreateMasterGain(this.context); if (master && this !== master) { return origConnect.call(this, master, ...args); } } } catch {} return origConnect.apply(this, [destination, ...args]); }; // 拦截 HTMLMediaElement 播放,保持音量同步 const origMediaPlay = HTMLMediaElement.prototype.play; HTMLMediaElement.prototype.play = function () { activeMediaElements.add(this); const isMuted = localStorage.getItem(STORAGE_KEY_MUTE) === '1'; try { this.volume = isMuted ? 0 : (currentVolume / 100); } catch {} return origMediaPlay.apply(this, arguments); }; // 跟踪动态创建的 Audio 实例 if (typeof window.Audio === 'function') { const OrigAudio = window.Audio; window.Audio = function (...args) { const audio = new OrigAudio(...args); activeMediaElements.add(audio); const isMuted = localStorage.getItem(STORAGE_KEY_MUTE) === '1'; try { audio.volume = isMuted ? 0 : (currentVolume / 100); } catch {} return audio; }; window.Audio.prototype = OrigAudio.prototype; } // 全局下发音量与静音状态 function setMasterGain(valPercent) { const isMuted = localStorage.getItem(STORAGE_KEY_MUTE) === '1'; const targetGain = isMuted ? 0 : (valPercent / 100); // 1. 同步 Web Audio 增益 allMasterGainNodes.forEach((gainNode) => { try { gainNode.gain.setValueAtTime(targetGain, gainNode.context.currentTime); } catch { try { gainNode.gain.value = targetGain; } catch {} } }); // 2. 同步动态与内存中的媒体元素 activeMediaElements.forEach((el) => { try { el.volume = targetGain; } catch {} }); // 3. 同步页面现存音视频元素 document.querySelectorAll('audio, video').forEach((el) => { try { el.volume = targetGain; activeMediaElements.add(el); } catch {} }); // 4. 同步挂载在音乐按钮属性中的音频实例 document.querySelectorAll('[data-slot-music]').forEach((btn) => { if (btn.audio) { try { btn.audio.volume = targetGain; activeMediaElements.add(btn.audio); } catch {} } }); } // --- 3. UI 样式注入 --- const styleEl = document.createElement('style'); styleEl.id = 'sb-game-vol-custom-style'; styleEl.textContent = ` .wheel-panel > .sb-vol-popup, .form-panel > .sb-vol-popup, .ddz-panel > .sb-vol-popup, .sb-vol-popup { position: absolute !important; z-index: 99999 !important; width: 250px; padding: 12px 14px; background: rgba(13, 29, 51, 0.96); border: 1px solid rgba(217, 164, 65, 0.7); border-radius: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.75), 0 0 12px rgba(217, 164, 65, 0.25); backdrop-filter: blur(10px); display: none; flex-direction: column; gap: 10px; color: #ffe9a8; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", sans-serif; box-sizing: border-box; user-select: none; animation: sbFadeIn 0.18s cubic-bezier(0.16, 1, 0.3, 1); } .wheel-panel > .sb-vol-popup.is-open, .form-panel > .sb-vol-popup.is-open, .ddz-panel > .sb-vol-popup.is-open, .sb-vol-popup.is-open { display: flex !important; } @keyframes sbFadeIn { from { opacity: 0; transform: translateY(-4px) scale(0.96); } to { opacity: 1; transform: translateY(0) scale(1); } } .sb-vol-header { display: flex; justify-content: space-between; align-items: center; font-size: 13px; font-weight: 600; letter-spacing: 0.5px; } .sb-vol-percent { color: #ffd02f; font-family: monospace; font-size: 14px; } .sb-vol-main-row { display: flex; align-items: center; gap: 10px; } .sb-vol-mute-btn { flex-shrink: 0; width: 32px; height: 32px; padding: 0; border: 1px solid rgba(217, 164, 65, 0.5); border-radius: 50%; background: rgba(212, 42, 32, 0.85); color: #ffe9a8; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: all 0.2s; } .sb-vol-mute-btn:hover { background: rgba(227, 55, 44, 1); transform: scale(1.05); } .sb-vol-mute-btn.is-muted { background: rgba(95, 95, 95, 0.85); border-color: rgba(189, 189, 189, 0.5); color: #ccc; } .sb-vol-mute-btn svg { width: 18px; height: 18px; } .sb-vol-range { flex: 1; height: 6px; -webkit-appearance: none; background: rgba(255, 255, 255, 0.15); border-radius: 3px; outline: none; cursor: pointer; transition: background 0.2s; } .sb-vol-range::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: #ffd02f; border: 2px solid #fff; box-shadow: 0 0 6px rgba(0,0,0,0.5); cursor: pointer; transition: transform 0.1s; } .sb-vol-range::-webkit-slider-thumb:hover { transform: scale(1.15); } .sb-vol-presets { display: flex; gap: 6px; justify-content: space-between; } .sb-vol-preset-btn { flex: 1; padding: 4px 0; font-size: 11px; border: 1px solid rgba(217, 164, 65, 0.35); border-radius: 4px; background: rgba(255, 255, 255, 0.06); color: #ffe9a8; cursor: pointer; transition: all 0.15s; text-align: center; } .sb-vol-preset-btn:hover { background: rgba(217, 164, 65, 0.2); border-color: #ffd02f; } .sb-vol-preset-btn.is-active { background: #ffd02f; color: #0d1d33; font-weight: 700; border-color: #ffd02f; } `; function injectStyle() { if (!document.getElementById('sb-game-vol-custom-style')) { (document.head || document.documentElement).appendChild(styleEl); } } injectStyle(); // --- 4. 弹窗 UI 构建与交互控制 --- let popup = null; let slider = null; let percentTxt = null; let innerMuteBtn = null; let iconOn = null; let iconOff = null; let presetBtns = []; let activeMuteBtn = null; let suppressCapture = false; // 创建弹窗 DOM 结构 function createPopup(container) { if (document.getElementById('sb-volume-popup')) { popup = document.getElementById('sb-volume-popup'); return popup; } popup = document.createElement('div'); popup.id = 'sb-volume-popup'; popup.className = 'sb-vol-popup'; popup.innerHTML = ` <div class="sb-vol-header"> <span>🎮 游戏音量</span> <span class="sb-vol-percent" id="sb-vol-percent-txt">${currentVolume}%</span> </div> <div class="sb-vol-main-row"> <button type="button" class="sb-vol-mute-btn" id="sb-vol-inner-mute" title="静音切换"> <svg class="sb-icon-on" viewBox="0 0 24 24" fill="currentColor"> <path d="M4 9h3.5L12 5v14l-4.5-4H4z"></path> <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" d="M15.5 9a4 4 0 010 6M18 6.5a7.5 7.5.0 010 11"></path> </svg> <svg class="sb-icon-off" viewBox="0 0 24 24" fill="currentColor" style="display:none;"> <path d="M4 9h3.5L12 5v14l-4.5-4H4z"></path> <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" d="m16 9.5 5 5m0-5-5 5"></path> </svg> </button> <input type="range" class="sb-vol-range" id="sb-vol-slider" min="0" max="100" value="${currentVolume}"> </div> <div class="sb-vol-presets"> <button type="button" class="sb-vol-preset-btn ${currentVolume === 30 ? 'is-active' : ''}" data-val="30">30%</button> <button type="button" class="sb-vol-preset-btn ${currentVolume === 60 ? 'is-active' : ''}" data-val="60">60%</button> <button type="button" class="sb-vol-preset-btn ${currentVolume === 80 ? 'is-active' : ''}" data-val="80">80%</button> <button type="button" class="sb-vol-preset-btn ${currentVolume === 100 ? 'is-active' : ''}" data-val="100">100%</button> </div> `; (container || document.body).appendChild(popup); slider = popup.querySelector('#sb-vol-slider'); percentTxt = popup.querySelector('#sb-vol-percent-txt'); innerMuteBtn = popup.querySelector('#sb-vol-inner-mute'); iconOn = innerMuteBtn.querySelector('.sb-icon-on'); iconOff = innerMuteBtn.querySelector('.sb-icon-off'); presetBtns = popup.querySelectorAll('.sb-vol-preset-btn'); // 内部静音按钮:触发原按钮静音切换 innerMuteBtn.addEventListener('click', function (e) { e.stopPropagation(); const btn = activeMuteBtn || document.querySelector(MUTE_BTN_SELECTOR); if (btn) { suppressCapture = true; btn.click(); } else { const wasMuted = localStorage.getItem(STORAGE_KEY_MUTE) === '1'; localStorage.setItem(STORAGE_KEY_MUTE, wasMuted ? '0' : '1'); } setTimeout(syncMuteUI, 30); }); // 滑块滑动调节 slider.addEventListener('input', (e) => applyVolume(e.target.value)); // 预设音量按钮点击 presetBtns.forEach((btn) => { btn.addEventListener('click', (e) => { e.stopPropagation(); applyVolume(btn.dataset.val); }); }); syncMuteUI(); return popup; } // 同步静音图标及高亮状态 function syncMuteUI() { if (!innerMuteBtn || !iconOn || !iconOff) return; const btn = activeMuteBtn || document.querySelector(MUTE_BTN_SELECTOR); const isMuted = btn ? (btn.classList.contains('is-muted') || btn.getAttribute('aria-pressed') === 'true' || localStorage.getItem(STORAGE_KEY_MUTE) === '1') : (localStorage.getItem(STORAGE_KEY_MUTE) === '1'); innerMuteBtn.classList.toggle('is-muted', isMuted); iconOn.style.display = isMuted ? 'none' : 'block'; iconOff.style.display = isMuted ? 'block' : 'none'; setMasterGain(currentVolume); } // 应用音量数值 function applyVolume(val) { currentVolume = parseInt(val, 10); if (slider) slider.value = currentVolume; if (percentTxt) percentTxt.textContent = `${currentVolume}%`; localStorage.setItem(STORAGE_KEY_VOLUME, currentVolume); const btn = activeMuteBtn || document.querySelector(MUTE_BTN_SELECTOR); const isMuted = btn ? (btn.classList.contains('is-muted') || btn.getAttribute('aria-pressed') === 'true' || localStorage.getItem(STORAGE_KEY_MUTE) === '1') : (localStorage.getItem(STORAGE_KEY_MUTE) === '1'); // 静音时增加音量自动解除静音;降至 0 自动静音 if (currentVolume > 0 && isMuted && btn) { suppressCapture = true; btn.click(); } else if (currentVolume === 0 && !isMuted && btn) { suppressCapture = true; btn.click(); } setMasterGain(currentVolume); presetBtns.forEach((pBtn) => { pBtn.classList.toggle('is-active', parseInt(pBtn.dataset.val, 10) === currentVolume); }); syncMuteUI(); } // 挂载弹窗至对应容器 function ensurePopupAttached(targetBtn) { const container = (targetBtn && targetBtn.closest(CONTAINER_SELECTOR)) || document.querySelector(CONTAINER_SELECTOR) || (targetBtn && targetBtn.parentElement) || document.body; if (!popup) { createPopup(container); } else if (popup.parentElement !== container) { container.appendChild(popup); } } // 弹窗定位:吸附于音效按钮正下方 function updatePosition(targetBtn) { const btn = targetBtn || activeMuteBtn || document.querySelector(MUTE_BTN_SELECTOR); if (!btn || !popup || !popup.parentElement) return; const cRect = popup.parentElement.getBoundingClientRect(); const mRect = btn.getBoundingClientRect(); const offsetTopInContainer = mRect.top - cRect.top; const offsetRightInContainer = cRect.right - mRect.right; popup.style.top = `${offsetTopInContainer + mRect.height + 8}px`; popup.style.right = `${Math.max(8, offsetRightInContainer)}px`; popup.style.left = 'auto'; } function openPopup(targetBtn) { ensurePopupAttached(targetBtn); popup.classList.add('is-open'); syncMuteUI(); updatePosition(targetBtn); } function closePopup() { if (popup) popup.classList.remove('is-open'); } function togglePopup(targetBtn) { if (popup && popup.classList.contains('is-open')) { closePopup(); } else { openPopup(targetBtn); } } // --- 5. 事件监听与委托 --- // 全局捕获阶段拦截音效按钮点击 document.addEventListener( 'click', function (e) { const muteBtn = e.target.closest && e.target.closest(MUTE_BTN_SELECTOR); if (!muteBtn) return; // 内部触发时放行原生点击 if (suppressCapture) { suppressCapture = false; return; } e.stopImmediatePropagation(); e.preventDefault(); activeMuteBtn = muteBtn; togglePopup(muteBtn); }, true ); // 点击外部区域自动关闭 document.addEventListener('click', function (e) { if (!popup || !popup.classList.contains('is-open')) return; if (!popup.contains(e.target) && !(activeMuteBtn && activeMuteBtn.contains(e.target))) { closePopup(); } }); // ESC 键关闭 document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && popup && popup.classList.contains('is-open')) { closePopup(); } }); // 窗口大小变化重新定位 window.addEventListener('resize', () => { if (popup && popup.classList.contains('is-open')) updatePosition(); }); // --- 6. DOM 初始化与观察 --- function safeInit() { injectStyle(); const muteBtn = document.querySelector(MUTE_BTN_SELECTOR); if (muteBtn) { ensurePopupAttached(muteBtn); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', safeInit); } else { safeInit(); } // 监听 DOM 树加载与节点动态注入 const observer = new MutationObserver(() => { injectStyle(); if (!document.getElementById('sb-volume-popup') && document.querySelector(MUTE_BTN_SELECTOR)) { safeInit(); } }); const targetRoot = document.documentElement || document.body; if (targetRoot) { try { observer.observe(targetRoot, { childList: true, subtree: true }); } catch (e) { document.addEventListener('DOMContentLoaded', () => { observer.observe(document.body, { childList: true, subtree: true }); }); } } })();收藏支持反对打赏个性签名我是个性签名
那其它软件的音量不是一起调了


