// ==UserScript== // @name B站直播间音量快捷控制 // @namespace http://tampermonkey.net/ // @version 1.2.0 // @description 快捷调整B站直播间音量,支持循环切换和弹出菜单两种模式,支持深色模式,设置即时生效 // @author Super-Dian // @match https://live.bilibili.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // ==================== 深色模式检测 ==================== function isDarkMode() { // 检查B站的深色模式设置 const html = document.documentElement; // B站深色模式通常通过 class="dark" 或 data-theme="dark" 标记 if (html.classList.contains('dark') || html.getAttribute('data-theme') === 'dark' || html.getAttribute('data-darkmode') === 'true') { return true; } // 检查系统深色模式偏好 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { return true; } return false; } // 获取当前主题颜色 function getThemeColors() { const dark = isDarkMode(); return { isDark: dark, bgPrimary: dark ? '#1f1f1f' : '#ffffff', bgSecondary: dark ? '#2a2a2a' : '#f5f5f5', bgTertiary: dark ? '#353535' : '#e8e8e8', textPrimary: dark ? '#e0e0e0' : '#333333', textSecondary: dark ? '#a0a0a0' : '#666666', border: dark ? '#404040' : '#dddddd', accent: '#fb7299', // B站粉色保持不变 accentHover: dark ? '#fc9db8' : '#fb7299', overlay: dark ? 'rgba(0, 0, 0, 0.85)' : 'rgba(0, 0, 0, 0.9)' }; } // ==================== 配置 ==================== const DEFAULT_CONFIG = { // 预设音量值(UI显示的0-100) presetVolumes: [0, 10, 30, 50, 70, 100], // 切换模式: 'cycle' 循环切换, 'menu' 弹出菜单 switchMode: 'cycle', // 按钮位置 buttonPosition: 'left-bottom', // 按钮透明度 buttonOpacity: 0.7, // 当前音量索引(用于循环模式) currentIndex: 2 }; let config = loadConfig(); // ==================== 配置管理 ==================== function loadConfig() { const saved = GM_getValue('volume_config', null); if (saved) { return { ...DEFAULT_CONFIG, ...JSON.parse(saved) }; } return { ...DEFAULT_CONFIG }; } function saveConfig(newConfig) { config = newConfig; GM_setValue('volume_config', JSON.stringify(config)); applySettings(); } // 应用设置到UI function applySettings() { // 更新主按钮透明度 if (mainButton) { mainButton.style.opacity = config.buttonOpacity.toString(); } // 重建弹出菜单(如果已创建) if (popupMenu) { const wasVisible = popupMenu.style.display !== 'none'; popupMenu.remove(); popupMenu = null; if (wasVisible || config.switchMode === 'menu') { createPopupMenu(); } } // 更新样式 injectStyles(); console.log('[音量控制] 设置已应用'); } // ==================== 音量控制 ==================== function getVideoElement() { return document.querySelector('video'); } function getCurrentVolume() { const video = getVideoElement(); if (!video) return null; return Math.round(video.volume * 100); // 转换为0-100 } function setVolume(percent) { const video = getVideoElement(); if (!video) { console.warn('[音量控制] 未找到视频元素'); return false; } // 限制范围 0-100 percent = Math.max(0, Math.min(100, percent)); video.volume = percent / 100; // 转换为0-1 video.muted = percent === 0; console.log(`[音量控制] 音量已设置为: ${percent}%`); updateButtonDisplay(); return true; } function getMuted() { const video = getVideoElement(); return video ? video.muted : false; } function setMuted(muted) { const video = getVideoElement(); if (video) { video.muted = muted; } } // ==================== 循环切换 ==================== function cycleVolume() { const { presetVolumes } = config; if (presetVolumes.length === 0) return; // 获取当前音量在预设列表中的位置 const currentVol = getCurrentVolume(); let currentIndex = presetVolumes.indexOf(currentVol); // 如果当前音量不在预设列表中,从头开始 if (currentIndex === -1) { currentIndex = -1; } // 切换到下一个 const nextIndex = (currentIndex + 1) % presetVolumes.length; config.currentIndex = nextIndex; setVolume(presetVolumes[nextIndex]); } // ==================== UI 创建 ==================== let mainButton = null; let popupMenu = null; let settingsPanel = null; function createMainButton() { if (mainButton) return mainButton; mainButton = document.createElement('div'); mainButton.id = 'volume-quick-btn'; mainButton.innerHTML = ` ${getCurrentVolume() || 0} `; mainButton.title = '左键:切换音量\n右键:打开设置'; mainButton.style.opacity = config.buttonOpacity.toString(); // 左键点击 - 根据模式切换 mainButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); if (config.switchMode === 'cycle') { cycleVolume(); } else { togglePopupMenu(); } }); // 右键点击 - 打开设置 mainButton.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); toggleSettingsPanel(); }); document.body.appendChild(mainButton); return mainButton; } function updateButtonDisplay() { const display = document.getElementById('volume-display'); if (display) { display.textContent = getCurrentVolume() || 0; } } // ==================== 弹出菜单 ==================== function createPopupMenu() { if (popupMenu) return popupMenu; popupMenu = document.createElement('div'); popupMenu.id = 'volume-popup-menu'; popupMenu.style.display = 'none'; const menuContent = document.createElement('div'); menuContent.className = 'volume-menu-content'; // 添加音量按钮 config.presetVolumes.forEach((vol, index) => { const btn = document.createElement('button'); btn.textContent = `${vol}%`; btn.className = 'volume-menu-item'; btn.addEventListener('click', (e) => { e.stopPropagation(); setVolume(vol); hidePopupMenu(); }); menuContent.appendChild(btn); }); popupMenu.appendChild(menuContent); document.body.appendChild(popupMenu); return popupMenu; } function togglePopupMenu() { if (!popupMenu) createPopupMenu(); if (popupMenu.style.display === 'none') { // 隐藏设置面板 if (settingsPanel) settingsPanel.style.display = 'none'; popupMenu.style.display = 'block'; } else { popupMenu.style.display = 'none'; } } function hidePopupMenu() { if (popupMenu) popupMenu.style.display = 'none'; } // ==================== 样式管理 ==================== let dynamicStyle = null; function injectStyles() { if (dynamicStyle) dynamicStyle.remove(); const colors = getThemeColors(); dynamicStyle = document.createElement('style'); dynamicStyle.id = 'volume-control-styles'; dynamicStyle.textContent = ` /* 设置面板 */ #volume-settings-panel { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 350px; max-height: 80vh; background-color: ${colors.bgPrimary}; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); z-index: 999999; overflow: hidden; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; border: 1px solid ${colors.border}; } #volume-settings-panel .settings-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: linear-gradient(135deg, #fb7299, #fc9db8); color: #fff; } #volume-settings-panel .settings-header h3 { margin: 0; font-size: 16px; } #volume-settings-panel .settings-close { background: none; border: none; color: #fff; font-size: 24px; cursor: pointer; padding: 0; line-height: 1; } #volume-settings-panel .settings-close:hover { opacity: 0.8; } #volume-settings-panel .settings-body { padding: 20px; max-height: 60vh; overflow-y: auto; } #volume-settings-panel .settings-group { margin-bottom: 20px; } #volume-settings-panel .settings-group > label { display: block; font-weight: 600; margin-bottom: 10px; color: ${colors.textPrimary}; } #volume-settings-panel .radio-group { display: flex; gap: 20px; } #volume-settings-panel .radio-group label { display: flex; align-items: center; gap: 5px; cursor: pointer; color: ${colors.textPrimary}; } #volume-settings-panel .preset-item { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; padding: 8px; background: ${colors.bgSecondary}; border-radius: 6px; } #volume-settings-panel .preset-item input { flex: 1; padding: 6px 10px; border: 1px solid ${colors.border}; border-radius: 4px; font-size: 14px; background: ${colors.bgPrimary}; color: ${colors.textPrimary}; } #volume-settings-panel .preset-item input:focus { outline: none; border-color: ${colors.accent}; } #volume-settings-panel .preset-item .remove-btn { background: #ff4757; color: #fff; border: none; width: 24px; height: 24px; border-radius: 50%; cursor: pointer; font-size: 14px; } #volume-settings-panel .preset-item .remove-btn:hover { opacity: 0.8; } #volume-settings-panel .settings-btn { padding: 8px 16px; border: 1px solid ${colors.border}; border-radius: 6px; cursor: pointer; font-size: 14px; background: ${colors.bgPrimary}; color: ${colors.textPrimary}; transition: all 0.2s; } #volume-settings-panel .settings-btn:hover { background: ${colors.bgSecondary}; } #volume-settings-panel .settings-btn.primary { background: ${colors.accent}; color: #fff; border: none; } #volume-settings-panel .settings-btn.primary:hover { background: ${colors.accentHover}; } #volume-settings-panel .settings-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; padding-top: 15px; border-top: 1px solid ${colors.border}; } #volume-settings-panel input[type="range"] { width: 100%; margin-top: 5px; accent-color: ${colors.accent}; } #volume-settings-panel #opacity-value { font-size: 14px; color: ${colors.textSecondary}; } /* 弹出菜单 */ #volume-popup-menu { position: fixed; left: 20px; bottom: 80px; background: ${colors.overlay}; border-radius: 8px; padding: 8px; z-index: 99998; box-shadow: 0 4px 15px rgba(0,0,0,0.4); border: 1px solid ${colors.border}; } #volume-popup-menu .volume-menu-content { display: flex; flex-direction: column; gap: 5px; } #volume-popup-menu .volume-menu-item { padding: 10px 20px; background: transparent; border: none; color: ${colors.textPrimary}; cursor: pointer; border-radius: 6px; font-size: 14px; text-align: center; transition: background 0.2s; } #volume-popup-menu .volume-menu-item:hover { background: ${colors.accent}; color: #fff; } /* 主按钮 */ #volume-quick-btn { position: fixed; left: 20px; bottom: 20px; width: 50px; height: 50px; background-color: ${colors.isDark ? 'rgba(50, 50, 50, 0.9)' : 'rgba(0, 0, 0, 0.7)'}; border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: pointer; z-index: 99999; color: #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: all 0.2s ease; user-select: none; border: 2px solid ${colors.isDark ? 'rgba(255,255,255,0.1)' : 'rgba(255,255,255,0.2)'}; } #volume-quick-btn:hover { transform: scale(1.1); opacity: 1 !important; box-shadow: 0 4px 15px rgba(251, 114, 153, 0.5); } #volume-quick-btn #volume-display { font-size: 10px; font-weight: bold; margin-top: 2px; } `; document.head.appendChild(dynamicStyle); } // 监听主题变化 function watchThemeChanges() { // 监听 html class 变化 const observer = new MutationObserver(() => { injectStyles(); }); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme', 'data-darkmode'] }); // 监听系统深色模式变化 if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { injectStyles(); }); } } // ==================== 设置面板 ==================== function createSettingsPanel() { if (settingsPanel) return settingsPanel; settingsPanel = document.createElement('div'); settingsPanel.id = 'volume-settings-panel'; settingsPanel.style.display = 'none'; settingsPanel.innerHTML = `