您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Volume % badge
// ==UserScript== // @name Show YouTube Volume % Badge // @namespace yt.volbadge.icon // @version 1.0 // @description Volume % badge // @match *://*.youtube.com/* // @match *://youtu.be/* // @grant none // @run-at document-idle // @license MIT // ==/UserScript== (() => { const BADGE_CLASS = 'ytp-volbadge'; const STYLE_ID = 'ytp-volbadge-style'; const stateByPlayer = new WeakMap(); function injectStyle() { if (document.getElementById(STYLE_ID)) return; const s = document.createElement('style'); s.id = STYLE_ID; s.textContent = ` .ytp-volume-icon { position: relative !important; } .${BADGE_CLASS}{ position: absolute; top: -2px; right: 2px; height: 16px; line-height: 16px; padding: 0 6px; border-radius: 10px; background: rgba(0,0,0,.65); color: #fff; font-size: 10px; font-family: Roboto, Arial, Helvetica, sans-serif; user-select: none; pointer-events: none; white-space: nowrap; }`; document.head.appendChild(s); } function getMainPlayer() { const list = Array.from(document.querySelectorAll('.html5-video-player')); return list.find(el => el.classList.contains('playing-mode') || el.classList.contains('paused-mode')) || list[0] || null; } function attachToPlayer(player) { injectStyle(); let st = stateByPlayer.get(player); if (!st) { st = {}; stateByPlayer.set(player, st); } const video = player.querySelector('video'); const muteBtn = player.querySelector('.ytp-mute-button'); const volumePanel = player.querySelector('.ytp-volume-panel'); const ensureBadge = () => { const icon = player.querySelector('.ytp-volume-icon'); if (!icon) return null; let badge = icon.querySelector('.' + BADGE_CLASS); if (!badge) { badge = document.createElement('span'); badge.className = BADGE_CLASS; badge.textContent = '--%'; icon.appendChild(badge); } st.icon = icon; st.badge = badge; return badge; }; const getSlider = () => volumePanel?.querySelector('[role="slider"][aria-valuenow]') || null; const compute = () => { if (!st.badge) return; const muted = (muteBtn?.getAttribute('aria-pressed') === 'true') || (video?.muted ?? false); let val = 0; if (st.slider && st.slider.hasAttribute('aria-valuenow')) { const raw = parseInt(st.slider.getAttribute('aria-valuenow') || '0', 10); val = isNaN(raw) ? 0 : raw; } else if (video) { val = Math.round((video.muted ? 0 : video.volume) * 100); } st.badge.textContent = `${muted ? 0 : val}`; st.badge.title = muted ? 'Muted' : `Volume: ${val}%`; }; const bindSliderObs = () => { if (st.sliderObs) st.sliderObs.disconnect(); st.slider = getSlider(); if (!st.slider) return; st.sliderObs = new MutationObserver(muts => { if (muts.some(m => m.attributeName === 'aria-valuenow')) compute(); }); st.sliderObs.observe(st.slider, { attributes: true, attributeFilter: ['aria-valuenow'] }); compute(); }; // First ensure badge + slider ensureBadge(); bindSliderObs(); compute(); // Bind once: mute + video events + lazy slider creation on hover/focus if (!st.bound) { if (muteBtn) { const mo = new MutationObserver(muts => { if (muts.some(m => m.attributeName === 'aria-pressed')) compute(); }); mo.observe(muteBtn, { attributes: true, attributeFilter: ['aria-pressed'] }); } if (video) { ['volumechange', 'loadedmetadata', 'play'].forEach(ev => video.addEventListener(ev, compute, { passive: true }) ); } if (volumePanel) { volumePanel.addEventListener('mouseenter', bindSliderObs, { passive: true }); volumePanel.addEventListener('focusin', bindSliderObs, { passive: true }); } st.bound = true; } // Tiny, throttled observer on the player's controls only if (!st.controlsObs) { const controls = player.querySelector('.ytp-chrome-bottom'); if (controls) { let scheduled = false; const scheduleEnsure = () => { if (scheduled) return; scheduled = true; requestAnimationFrame(() => { scheduled = false; // If icon or badge got replaced/removed, restore ensureBadge(); // If slider node got swapped, rebind bindSliderObs(); compute(); }); }; st.controlsObs = new MutationObserver(scheduleEnsure); st.controlsObs.observe(controls, { childList: true, subtree: true }); } } } function bootstrap() { const p = getMainPlayer(); if (p) attachToPlayer(p); } // Start + keep alive across SPA navigations bootstrap(); window.addEventListener('yt-navigate-finish', () => setTimeout(bootstrap, 150), { passive: true }); })();