// ==UserScript== // @name YouTube Global Volume Control // @namespace http://tampermonkey.net/ // @version 2.0 // @description Control YouTube volume up to 500% in 5% steps, regardless of player focus, with a fixed indicator in the center of the screen. // @author motorrin // @match https://*.youtube.com/* // @grant unsafeWindow // @run-at document-start // ==/UserScript== (function() { 'use strict'; const w = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; let hud = null; let hudTimeout = null; let cachedPlayer = null; let cachedVideo = null; let audioCtx = null; function setupAudioContext(video) { if (!video) return null; if (video._gainNode) { if (audioCtx && audioCtx.state === 'suspended') { audioCtx.resume(); } return video._gainNode; } try { const AudioCtx = w.AudioContext || w.webkitAudioContext; if (!AudioCtx) return null; if (!audioCtx) { audioCtx = new AudioCtx(); } if (audioCtx.state === 'suspended') { audioCtx.resume(); } const source = audioCtx.createMediaElementSource(video); const gainNode = audioCtx.createGain(); source.connect(gainNode); gainNode.connect(audioCtx.destination); video._gainNode = gainNode; video._audioSource = source; return gainNode; } catch (err) { console.error('[YT Volume Control] AudioContext error:', err); return null; } } function showVolumeHUD(volume) { const doc = w.document; const parent = doc.fullscreenElement || doc.body; if (!parent) return; if (!hud) { hud = doc.createElement('div'); hud.id = 'yt-global-volume-hud'; hud.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.8); color: #ffffff; font-size: 28px; font-weight: bold; font-family: "YouTube Sans", Roboto, Arial, sans-serif; padding: 10px 22px; border-radius: 8px; z-index: 2147483647; pointer-events: none; opacity: 0; user-select: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6); letter-spacing: 0.5px; `; } if (hud.parentNode !== parent) { parent.appendChild(hud); } let icon = '🔊'; if (volume === 0) icon = '🔇'; else if (volume < 35) icon = '🔈'; else if (volume < 70) icon = '🔉'; else if (volume > 100) icon = '⚡'; hud.textContent = `${icon} ${volume}%`; hud.style.transition = 'none'; hud.style.opacity = '1'; void hud.offsetHeight; hud.style.transition = 'opacity 0.15s ease-out'; if (hudTimeout) { clearTimeout(hudTimeout); } hudTimeout = setTimeout(() => { hud.style.opacity = '0'; }, 900); } function getNextVolume(current, isUp) { const rounded = Math.round(current); return isUp ? Math.min(500, Math.floor(rounded / 5) * 5 + 5) : Math.max(0, Math.ceil(rounded / 5) * 5 - 5); } w.addEventListener('keydown', function(e) { if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return; if (w.location.pathname.startsWith('/shorts/')) return; const active = w.document.activeElement; if (active) { const role = active.getAttribute('role'); if ( active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.tagName === 'SELECT' || active.isContentEditable || role === 'textbox' || role === 'combobox' || role === 'slider' || role === 'menuitem' || role === 'menuitemradio' || role === 'menuitemcheckbox' || role === 'option' ) { return; } } if (!cachedPlayer || !cachedPlayer.isConnected) { cachedPlayer = w.document.getElementById('movie_player') || w.document.querySelector('.html5-video-player'); } if (!cachedVideo || !cachedVideo.isConnected) { cachedVideo = w.document.querySelector('video'); } const player = cachedPlayer; const video = cachedVideo; if (!player && !video) return; const isWatchPage = w.location.pathname.startsWith('/watch') || w.location.pathname.startsWith('/embed/'); const isPlaying = video && !video.paused; if (!isWatchPage && !isPlaying) return; e.preventDefault(); e.stopImmediatePropagation(); const isUp = e.key === 'ArrowUp'; let currentVolume = typeof video._customVolume === 'number' ? video._customVolume : 100; if (typeof video._customVolume !== 'number') { if (player && typeof player.getVolume === 'function') { currentVolume = player.getVolume(); } else if (video) { currentVolume = video.volume * 100; } } const nextVolume = getNextVolume(currentVolume, isUp); video._customVolume = nextVolume; if (player && typeof player.isMuted === 'function' && player.isMuted()) { if (typeof player.unMute === 'function') player.unMute(); } if (video && video.muted) { video.muted = false; } if (nextVolume <= 100) { if (player && typeof player.setVolume === 'function') { try { player.setVolume(nextVolume); } catch (err) {} } else if (video) { try { video.volume = nextVolume / 100; } catch (err) {} } if (video._gainNode) { try { video._gainNode.gain.value = 1.0; } catch (err) {} } } else { if (player && typeof player.setVolume === 'function') { try { player.setVolume(100); } catch (err) {} } else if (video) { try { video.volume = 1.0; } catch (err) {} } const gainNode = setupAudioContext(video); if (gainNode) { try { gainNode.gain.value = nextVolume / 100; } catch (err) {} } } showVolumeHUD(nextVolume); }, true); })();