您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
修复小键盘enter全萤幕问题。滚轮、013速度28音量5(空白键)播放暂停、enter全萤幕切换、小键盘+-增减10%进度。完整支援:YouTube、B站、Steam。B站直播(局部:012358/无滚轮enter)
当前为
// ==UserScript== // @name Scroll Volume Dx Edition // @name:zh-TW 滾動音量Dx版 // @name:zh-CN 滚动音量Dx版 // @namespace http://tampermonkey.net/ // @version 7.6 // @description Fixed numpad enter fullscreen issue. For recognized players, wheel scroll for volume, 013 for speed, 28 for volume, 5(space) for play/pause, enter for fullscreen. Fully supports: YouTube, Bilibili, Steam. Bilibili live (partial:012358/no wheel/enter) // @description:zh-TW 修復小鍵盤enter全螢幕問題。滾輪、013速度28音量5(空白鍵)播放暫停、enter全螢幕切換、小鍵盤+-增減10%進度。完整支援:YouTube、B站、Steam。B站直播(局部:012358/無滾輪enter) // @description:zh-CN 修复小键盘enter全萤幕问题。滚轮、013速度28音量5(空白键)播放暂停、enter全萤幕切换、小键盘+-增减10%进度。完整支援:YouTube、B站、Steam。B站直播(局部:012358/无滚轮enter) // @match *://*/* // @exclude *://www.facebook.com/* // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function() { 'use strict'; const VOLUME_STORAGE_PREFIX = 'volume_'; const PLATFORMS = { YOUTUBE: isYouTubeMain(), BILIBILI: isBilibili(), TWITCH: isTwitch(), STEAM: isSteam(), FULLSCREEN_API_SUPPORT: !!document.fullscreenEnabled }; function isBilibili() { return /bilibili\.com/.test(location.hostname); } function isYouTubeMain() { return /youtube\.com|youtu\.be/.test(location.hostname); } function isTwitch() { return /twitch\.tv/.test(location.hostname); } function isSteam() { return /steam(community|powered)\.com/.test(location.hostname); } function getYTPlayer() { return document.querySelector('ytd-player')?.getPlayer?.(); } let cachedVideoElement = null; function getVideoElement() { if (!cachedVideoElement) { const handler = getCurrentPlatformHandler(); cachedVideoElement = handler.getVideo(); } return cachedVideoElement; } function getDomainKey() { return VOLUME_STORAGE_PREFIX + window.location.hostname; } const adjustVolumeHandler = (video, delta) => { const volumeValue = PLATFORMS.YOUTUBE ? getYTPlayer()?.getVolume() : video.volume * 100; if (volumeValue === undefined) return; const newVolume = Math.max(0, Math.min(100, volumeValue + (delta > 0 ? -10 : 10))); PLATFORMS.YOUTUBE ? getYTPlayer()?.setVolume(newVolume) : (video.volume = newVolume / 100); showVolume(newVolume); GM_setValue(getDomainKey(), newVolume); return newVolume; }; function initVolumeMemory(video, retryCount = 0) { if (!video || retryCount > 3) return; const savedVolume = GM_getValue(getDomainKey()); if (savedVolume === undefined) return; const applyVolume = () => { try { if (PLATFORMS.YOUTUBE) { const player = getYTPlayer(); if (player?.setVolume) { player.setVolume(savedVolume); player.unMute(); } else { video.volume = savedVolume / 100; } } else { video.volume = savedVolume / 100; video.muted = false; } } catch (e) { console.warn('Volume restore failed:', e); } }; if (video.readyState < 2) { setTimeout(() => initVolumeMemory(video, retryCount + 1), 500); } else { applyVolume(); } } const PLATFORM_HANDLERS = { YOUTUBE: { getVideo: () => { const iframeVideo = findVideoInIframes(); return iframeVideo || document.querySelector('window.ytplayer,html5-video-player.ytp-player,ytd-player video'); }, adjustVolume: adjustVolumeHandler, toggleFullscreen: () => { const video = getVideoElement(); // 統一使用YouTube原生控制 if (document.querySelector('.ytp-fullscreen-button')) { simulateKeyPress('f', 70); } else { toggleNativeFullscreen(video); } }, specialKeys: { '7': () => { const player = getYTPlayer(); if (player?.getPlayerState() === 1) { document.querySelector('.ytp-prev-button')?.click(); } }, '9': () => { const player = getYTPlayer(); if (player?.getPlayerState() === 1) { document.querySelector('.ytp-next-button')?.click(); } }, 'NumpadEnter': () => simulateKeyPress('f', 70) // 將YT的numpadenter功能移到specialKeys } }, BILIBILI: { getVideo: () => { const iframeVideo = findVideoInIframes(); return iframeVideo || document.querySelector('.bpx-player-video-wrap video'); }, adjustVolume: adjustVolumeHandler, toggleFullscreen: (video) => { const fullscreenBtn = document.querySelector('.bpx-player-ctrl-full'); if (fullscreenBtn) { fullscreenBtn.click(); } else { toggleNativeFullscreen(video); } }, specialKeys: { '7': () => { const prevBtn = document.querySelector('.bpx-player-ctrl-eplist-prev'); if (prevBtn) prevBtn.click(); }, '9': () => { const nextBtn = document.querySelector('.bpx-player-ctrl-eplist-next'); if (nextBtn) nextBtn.click(); } } }, TWITCH: { getVideo: () => { const iframeVideo = findVideoInIframes(); return iframeVideo || document.querySelector('.video-ref video'); }, adjustVolume: adjustVolumeHandler, toggleFullscreen: (video) => { const fullscreenBtn = document.querySelector('[data-a-target="player-fullscreen-button"]'); if (fullscreenBtn) { fullscreenBtn.click(); } else { toggleNativeFullscreen(video); } }, specialKeys: { '7': () => simulateKeyPress('ArrowLeft', 37), '9': () => simulateKeyPress('ArrowRight', 39) } }, STEAM: { getVideo: () => { const iframeVideo = findVideoInIframes(); return iframeVideo || document.querySelector('video'); }, adjustVolume: adjustVolumeHandler, toggleFullscreen: (video) => { document.fullscreenElement ? document.exitFullscreen() : video.requestFullscreen?.(); }, specialKeys: { '7': () => { const video = getVideoElement(); if (video) video.currentTime -= 30; }, '9': () => { const video = getVideoElement(); if (video) video.currentTime += 30; } } }, GENERIC: { getVideo: () => { const iframeVideo = findVideoInIframes(); if (iframeVideo) return iframeVideo; const selectors = [ 'window.ytplayer', 'html5-video-player.ytp-player', 'div.html5-video-player', 'video', '.video-player video', '.video-js video', '.html5-video-container video', '.player-container video', '.media-container video', 'div.ytp-cued-thumbnail-overlay', '.video-container video' ]; for (const sel of selectors) { const el = document.querySelector(sel); if (el?.tagName === 'VIDEO') return el; } return null; }, adjustVolume: adjustVolumeHandler, toggleFullscreen: (video) => toggleNativeFullscreen(video), specialKeys: { '7': () => { const video = getVideoElement(); if (video) video.currentTime -= 10; }, '9': () => { const video = getVideoElement(); if (video) video.currentTime += 10; }, 'NumpadEnter': (video) => toggleNativeFullscreen(video) // 通用numpadenter功能 } } }; function findVideoInIframes() { const iframes = document.querySelectorAll('iframe'); for (const iframe of iframes) { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; const video = iframeDoc?.querySelector('video'); if (video) return video; } catch (e) { console.log('Cannot access iframe content:', e); } } return null; } function setupMutationObserver() { const handleIframeLoad = (iframe) => { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; const iframeVideo = iframeDoc?.querySelector('video'); if (iframeVideo) { cachedVideoElement = iframeVideo; initVideoHandlers(iframeVideo); } } catch (e) { console.log('Cannot access iframe content:', e); } }; const observer = new MutationObserver((mutations) => { const processedIframes = new Set(); for (const mutation of mutations) { if (mutation.type !== 'childList') continue; for (const node of mutation.addedNodes) { if (node.nodeName === 'VIDEO') { cachedVideoElement = node; initVideoHandlers(node); continue; } if (node.querySelector) { const video = node.querySelector('video'); if (video) { cachedVideoElement = video; initVideoHandlers(video); } const iframes = node.querySelectorAll('iframe:not([data-volume-observed])'); iframes.forEach(iframe => { if (processedIframes.has(iframe)) return; iframe.dataset.volumeObserved = 'true'; processedIframes.add(iframe); if (iframe.contentDocument) { handleIframeLoad(iframe); } iframe.addEventListener('load', () => handleIframeLoad(iframe)); }); } } } }); observer.observe(document.body, { childList: true, subtree: true }); } function initVideoHandlers(video) { if (!video) return; initVolumeMemory(video); video.addEventListener('play', () => { state.hasPlayed = true; }); } function toggleNativeFullscreen(video) { if (!video) return; try { if (document.fullscreenElement) { document.exitFullscreen(); } else if (video.requestFullscreen) { video.requestFullscreen(); } else if (video.webkitRequestFullscreen) { video.webkitRequestFullscreen(); } else if (video.msRequestFullscreen) { video.msRequestFullscreen(); } } catch (e) { console.error('Fullscreen error:', e); } } const state = { volumeAccumulator: 0, lastCustomRate: 1.0, hasPlayed: false, matchedContainer: null }; function simulateKeyPress(key, keyCode) { document.dispatchEvent(new KeyboardEvent('keydown', {key, keyCode, bubbles: true})); } function isInputElement(target) { return /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName) || target.isContentEditable; } function getCurrentPlatformHandler() { const platform = Object.keys(PLATFORMS).find(k => PLATFORMS[k]); return PLATFORM_HANDLERS[platform] || PLATFORM_HANDLERS.GENERIC; } function adjustRate(video, changeValue) { const newRate = Math.max(0.1, Math.min(16, video.playbackRate + changeValue )); video.playbackRate = parseFloat(newRate.toFixed(1)); state.lastCustomRate = newRate; showVolume(newRate * 100); } function togglePlaybackRate(video) { const currentRate = video.playbackRate; const newRate = currentRate === 1 ? state.lastCustomRate : 1; video.playbackRate = newRate; if (newRate !== 1) { state.lastCustomRate = currentRate; } showVolume(newRate * 100); } function showVolume(vol) { const display = document.getElementById('dynamic-volume-display') || createVolumeDisplay(); display.textContent = `${Math.round(vol)}%`; display.style.opacity = '1'; setTimeout(() => { display.style.opacity = '0'; }, 1000); } function createVolumeDisplay() { const display = document.createElement('div'); display.id = 'dynamic-volume-display'; Object.assign(display.style, { position: 'fixed', zIndex: 2147483647, top: '50%', left: '50%', transform: 'translate(-50%, -50%)', padding: '10px 20px', borderRadius: '8px', backgroundColor: 'rgba(0, 0, 0, 0.7)', color: '#fff', fontSize: '24px', fontFamily: 'Arial, sans-serif', opacity: '0', transition: 'opacity 1s', pointerEvents: 'none' }); document.body.appendChild(display); return display; } function handleWheel(e) { const video = getVideoElement(); if (!video || !isMouseOverVideo(e)) return; e.preventDefault(); const handler = getCurrentPlatformHandler(); handler.adjustVolume(video, e.deltaY); // 使用平台專用的adjustVolume處理 } function isMouseOverVideo(e) { const video = getVideoElement(); if (!video) return false; const rect = video.getBoundingClientRect(); return e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom; } function handleKeyEvent(e) { const video = getVideoElement(); if (!video || isInputElement(e.target)) return; const handler = getCurrentPlatformHandler(); if (handler.specialKeys && handler.specialKeys[e.key]) { handler.specialKeys[e.key](video); e.preventDefault(); return; } const actions = { 'Space': () => video[video.paused ? 'play' : 'pause'](), 'Numpad5': () => video[video.paused ? 'play' : 'pause'](), 'NumpadEnter': () => { // 保留numpadenter的預設功能 const handler = getCurrentPlatformHandler(); handler.toggleFullscreen?.(video); }, 'NumpadAdd': () => { video.currentTime += video.duration * 0.1; }, 'NumpadSubtract': () => { video.currentTime -= video.duration * 0.1; }, 'Numpad0': () => togglePlaybackRate(video), 'Numpad1': () => adjustRate(video, -0.1), 'Numpad3': () => adjustRate(video, 0.1), 'Numpad4': () => { video.currentTime -= 10; }, 'Numpad6': () => { video.currentTime += 10; } }; const action = actions[e.code] || (['+','-'].includes(e.key) && actions[`Numpad${e.key}`]); if (action) { action(); e.preventDefault(); } } function init() { document.addEventListener('wheel', handleWheel, { passive: false }); document.addEventListener('keydown', handleKeyEvent, true); const video = getVideoElement(); if (video) { initVideoHandlers(video); } setupMutationObserver(); document.querySelectorAll('iframe').forEach(iframe => { if (iframe.dataset.volumeObserved) return; iframe.dataset.volumeObserved = 'true'; iframe.addEventListener('load', () => { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow.document; const iframeVideo = iframeDoc.querySelector('video'); if (iframeVideo) { cachedVideoElement = iframeVideo; initVideoHandlers(iframeVideo); } } catch (e) { console.log('Cannot access iframe content:', e); } }); if (iframe.contentDocument) { const iframeVideo = iframe.contentDocument.querySelector('video'); if (iframeVideo) { cachedVideoElement = iframeVideo; initVideoHandlers(iframeVideo); } } }); } if (document.readyState !== 'loading') { init(); } else { document.addEventListener('DOMContentLoaded', init); } })();