// ==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 = `

音量快捷控制设置

${Math.round(config.buttonOpacity * 100)}%
`; document.body.appendChild(settingsPanel); bindSettingsEvents(); updatePresetList(); return settingsPanel; } function bindSettingsEvents() { // 关闭按钮 settingsPanel.querySelector('.settings-close').addEventListener('click', (e) => { e.stopPropagation(); settingsPanel.style.display = 'none'; }); // 透明度滑块 const opacitySlider = settingsPanel.querySelector('#opacity-slider'); const opacityValue = settingsPanel.querySelector('#opacity-value'); opacitySlider.addEventListener('input', () => { opacityValue.textContent = `${opacitySlider.value}%`; }); opacitySlider.addEventListener('click', (e) => { e.stopPropagation(); }); // 切换模式单选按钮 settingsPanel.querySelectorAll('input[name="switchMode"]').forEach(radio => { radio.addEventListener('click', (e) => { e.stopPropagation(); }); }); // 添加预设 settingsPanel.querySelector('#add-preset').addEventListener('click', (e) => { e.stopPropagation(); config.presetVolumes.push(50); updatePresetList(); }); // 保存设置 settingsPanel.querySelector('#save-settings').addEventListener('click', (e) => { e.stopPropagation(); const newConfig = { ...config }; newConfig.switchMode = settingsPanel.querySelector('input[name="switchMode"]:checked').value; newConfig.buttonOpacity = parseInt(opacitySlider.value) / 100; // 收集预设值 const presetInputs = settingsPanel.querySelectorAll('.preset-item input'); newConfig.presetVolumes = Array.from(presetInputs) .map(input => parseInt(input.value) || 0) .filter(v => v >= 0 && v <= 100) .sort((a, b) => a - b); saveConfig(newConfig); settingsPanel.style.display = 'none'; alert('设置已保存!'); }); // 恢复默认 settingsPanel.querySelector('#reset-settings').addEventListener('click', (e) => { e.stopPropagation(); if (confirm('确定要恢复默认设置吗?')) { saveConfig({ ...DEFAULT_CONFIG }); updatePresetList(); opacitySlider.value = DEFAULT_CONFIG.buttonOpacity * 100; opacityValue.textContent = `${DEFAULT_CONFIG.buttonOpacity * 100}%`; settingsPanel.querySelector(`input[name="switchMode"][value="${DEFAULT_CONFIG.switchMode}"]`).checked = true; } }); } function updatePresetList() { const presetList = settingsPanel.querySelector('#preset-list'); presetList.innerHTML = ''; config.presetVolumes.forEach((vol, index) => { const item = document.createElement('div'); item.className = 'preset-item'; item.innerHTML = ` `; // 输入框点击时阻止冒泡 item.querySelector('input').addEventListener('click', (e) => { e.stopPropagation(); }); // 删除按钮事件 item.querySelector('.remove-btn').addEventListener('click', (e) => { e.stopPropagation(); config.presetVolumes.splice(index, 1); updatePresetList(); }); presetList.appendChild(item); }); } function toggleSettingsPanel() { if (!settingsPanel) createSettingsPanel(); if (settingsPanel.style.display === 'none') { // 隐藏弹出菜单 hidePopupMenu(); settingsPanel.style.display = 'block'; } else { settingsPanel.style.display = 'none'; } } // ==================== 监听音量变化 ==================== function setupVolumeWatcher() { const checkInterval = setInterval(() => { const video = getVideoElement(); if (video) { clearInterval(checkInterval); // 监听音量变化 video.addEventListener('volumechange', () => { updateButtonDisplay(); }); // 初始更新 updateButtonDisplay(); } }, 500); } // ==================== 初始化 ==================== function init() { // 等待页面加载 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); return; } // 延迟初始化,确保视频元素已加载 setTimeout(() => { // 注入样式 injectStyles(); watchThemeChanges(); createMainButton(); setupVolumeWatcher(); // 如果是弹出菜单模式,预创建菜单 if (config.switchMode === 'menu') { createPopupMenu(); } console.log('[音量控制] 脚本已加载,当前主题:', isDarkMode() ? '深色' : '浅色'); }, 2000); } init(); })();