您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
滚轮、013速度、28音量、46+-5sec、5(空白键)播放暂停、enter全萤幕切换、小键盘+-增减10%进度。完整支援:YouTube、B站、Steam。B站直播(局部)
// ==UserScript== // @name 滾動音量Dx版 Scroll Volume Dx Edition // @name:zh-CN 滚动音量Dx版 // @name:en Scroll Volume Dx Edition // @namespace http://tampermonkey.net/ // @version 9.5.3 // @description 滾輪、013速度、28音量、46+-5sec、5(空白鍵)播放暫停、enter全螢幕切換、小鍵盤+-增減10%進度。完整支援:YouTube、B站、Steam。B站直播(局部) // @description:zh-CN 滚轮、013速度、28音量、46+-5sec、5(空白键)播放暂停、enter全萤幕切换、小键盘+-增减10%进度。完整支援:YouTube、B站、Steam。B站直播(局部) // @description:en wheel scroll for volume. NumpadKey:013 for speed, 28 for volume, 46 for 5sec、5(space) for play/pause, enter for fullscreen, numpad+- for 5sec. Fully supports: YouTube, Bilibili, Steam. Bilibili live (partial) // @match *://www.youtube.com/* // @match *://www.bilibili.com/* // @match *://live.bilibili.com/* // @match *://www.twitch.tv/* // @match *://store.steampowered.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (function() { 'use strict'; // 获取标准化的域名标识 (简化为二级域名) const getDomainId = () => { const hostParts = location.hostname.split('.'); return hostParts.length > 2 ? hostParts.slice(-2).join('_') : hostParts.join('_'); }; // 获取带域名的键名 const getDomainKey = (key) => `${key}_${getDomainId()}`; const initStorage = () => { const domainKey = getDomainKey('storageInitialized'); if (GM_getValue(domainKey) !== true) { GM_setValue(getDomainKey('stepTime'), 5); GM_setValue(getDomainKey('stepTimeLong'), 30); GM_setValue(getDomainKey('stepVolume'), 10); // 默认功能:1.长步进 GM_setValue(getDomainKey('key7Function'), 1); GM_setValue(getDomainKey('key9Function'), 1); GM_setValue(domainKey, true); } }; initStorage(); // 根据域名获取配置 const CONFIG = { stepTime: GM_getValue(getDomainKey('stepTime'), 5), stepTimeLong: GM_getValue(getDomainKey('stepTimeLong'), 30), stepVolume: GM_getValue(getDomainKey('stepVolume'), 10), key7Function: GM_getValue(getDomainKey('key7Function'), 1), key9Function: GM_getValue(getDomainKey('key9Function'), 1) }; // 配置菜单 GM_registerMenuCommand("⚙️ 設定步進", () => { const newVal = prompt("設定快進/快退", CONFIG.stepTime); if (newVal && !isNaN(newVal)) { GM_setValue(getDomainKey('stepTime'), parseFloat(newVal)); CONFIG.stepTime = parseFloat(newVal); } }); GM_registerMenuCommand("⏱️ 設定長步進", () => { const newVal = prompt("設定長跳轉", CONFIG.stepTimeLong); if (newVal && !isNaN(newVal)) { GM_setValue(getDomainKey('stepTimeLong'), parseFloat(newVal)); CONFIG.stepTimeLong = parseFloat(newVal); } }); GM_registerMenuCommand("🔊 設定音量步進", () => { const newVal = prompt("設定音量幅度 (%)", CONFIG.stepVolume); if (newVal && !isNaN(newVal)) { GM_setValue(getDomainKey('stepVolume'), parseFloat(newVal)); CONFIG.stepVolume = parseFloat(newVal); } }); GM_registerMenuCommand("🎛️ 設定按鍵7/9功能", () => { const options = { 1: '1. 長步進', 2: '2. 上一頁/下一頁', 3: '3. 上/下一個影片' // 修改功能名称 }; const message = `選擇按鍵7功能:\n${Object.values(options).join('\n')}`; const choice7 = prompt(message, CONFIG.key7Function); if (choice7 && options[choice7]) { GM_setValue(getDomainKey('key7Function'), parseInt(choice7)); CONFIG.key7Function = parseInt(choice7); } const choice9 = prompt(`選擇按鍵9功能:\n${Object.values(options).join('\n')}`, CONFIG.key9Function); if (choice9 && options[choice9]) { GM_setValue(getDomainKey('key9Function'), parseInt(choice9)); CONFIG.key9Function = parseInt(choice9); } }); let cachedVideo = null; let lastVideoCheck = 0; let videoElements = []; let currentVideoIndex = 0; let activeVideoId = null; // 新增:追踪当前控制的视频ID const PLATFORM = (() => { const host = location.hostname; if (/youtube\.com|youtu\.be/.test(host)) return "YOUTUBE"; if (/www.bilibili\.com/.test(host)) return "BILIBILI"; if (/twitch\.tv/.test(host)) return "TWITCH"; if (/steam(community|powered)\.com/.test(host)) return "STEAM"; return "GENERIC"; })(); const videoStateMap = new WeakMap(); function getVideoState(video) { if (!videoStateMap.has(video)) { videoStateMap.set(video, { lastCustomRate: 1.0, isDefaultRate: true }); } return videoStateMap.get(video); } // 生成视频唯一ID const generateVideoId = (video) => `${video.src}_${video.clientWidth}x${video.clientHeight}`; function getVideoElement() { // 优先使用当前激活的视频 if (activeVideoId) { const activeVideo = videoElements.find(v => generateVideoId(v) === activeVideoId); if (activeVideo && document.contains(activeVideo)) { cachedVideo = activeVideo; return cachedVideo; } } // 常规检测逻辑 if (cachedVideo && document.contains(cachedVideo) && (Date.now() - lastVideoCheck < 300)) { return cachedVideo; } const handler = PLATFORM_HANDLERS[PLATFORM] || PLATFORM_HANDLERS.GENERIC; cachedVideo = handler.getVideo(); lastVideoCheck = Date.now(); // 更新视频元素列表和当前索引 updateVideoElements(); if (cachedVideo && videoElements.length > 0) { currentVideoIndex = videoElements.indexOf(cachedVideo); if (currentVideoIndex === -1) currentVideoIndex = 0; activeVideoId = generateVideoId(cachedVideo); // 设置当前激活视频 } return cachedVideo; } function updateVideoElements() { videoElements = Array.from(document.querySelectorAll('video')) .filter(v => v.offsetParent !== null && v.readyState > 0); } function switchToNextVideo() { // 函数名称更新 if (videoElements.length < 2) return null; currentVideoIndex = (currentVideoIndex + 1) % videoElements.length; cachedVideo = videoElements[currentVideoIndex]; activeVideoId = generateVideoId(cachedVideo); // 更新激活视频ID lastVideoCheck = Date.now(); return cachedVideo; } function switchToPrevVideo() { // 函数名称更新 if (videoElements.length < 2) return null; currentVideoIndex = (currentVideoIndex - 1 + videoElements.length) % videoElements.length; cachedVideo = videoElements[currentVideoIndex]; activeVideoId = generateVideoId(cachedVideo); // 更新激活视频ID lastVideoCheck = Date.now(); return cachedVideo; } function commonAdjustVolume(video, delta) { if (delta < 0 && video.muted) { video.muted = false; } const newVolume = Math.max(0, Math.min(100, (video.volume * 100) + (delta > 0 ? -CONFIG.stepVolume : CONFIG.stepVolume) )); video.volume = newVolume / 100; showVolume(newVolume); return newVolume; } function clampVolume(vol) { return Math.round(Math.max(0, Math.min(100, vol)) * 100) / 100; } const PLATFORM_HANDLERS = { YOUTUBE: { getVideo: () => document.querySelector('video, ytd-player video') || findVideoInIframes(), adjustVolume: (video, delta) => { const ytPlayer = document.querySelector('#movie_player'); if (!ytPlayer?.getVolume) return commonAdjustVolume(video, delta); const currentVol = ytPlayer.getVolume(); const newVol = clampVolume(currentVol + (delta > 0 ? -CONFIG.stepVolume : CONFIG.stepVolume)); ytPlayer.setVolume(newVol); video.volume = newVol / 100; showVolume(newVol); }, toggleFullscreen: () => document.querySelector('.ytp-fullscreen-button')?.click(), specialKeys: { 'Space': () => {}, 'Numpad7': () => document.querySelector('.ytp-prev-button')?.click(), 'Numpad9': () => document.querySelector('.ytp-next-button')?.click() } }, BILIBILI: { getVideo: () => document.querySelector('.bpx-player-video-wrap video') || findVideoInIframes(), adjustVolume: commonAdjustVolume, toggleFullscreen: () => document.querySelector('.bpx-player-ctrl-full')?.click(), specialKeys: { 'Space': () => {}, 'Numpad2': () => {}, 'Numpad8': () => {}, 'Numpad4': () => {}, 'Numpad6': () => {}, 'Numpad7': () => document.querySelector('.bpx-player-ctrl-prev')?.click(), 'Numpad9': () => document.querySelector('.bpx-player-ctrl-next')?.click() } }, TWITCH: { getVideo: () => document.querySelector('.video-ref video') || findVideoInIframes(), adjustVolume: commonAdjustVolume, toggleFullscreen: () => document.querySelector('[data-a-target="player-fullscreen-button"]')?.click(), specialKeys: { 'Numpad7': () => simulateKeyPress('ArrowLeft'), 'Numpad9': () => simulateKeyPress('ArrowRight') } }, STEAM: { getVideo: () => { const videos = Array.from(document.querySelectorAll('video')); const playingVideo = videos.find(v => v.offsetParent !== null && !v.paused); if (playingVideo) return playingVideo; const visibleVideo = videos.find(v => v.offsetParent !== null); if (visibleVideo) return visibleVideo; return findVideoInIframes(); }, adjustVolume: commonAdjustVolume, toggleFullscreen: (video) => { if (!video) return; const container = video.closest('.game_hover_activated') || video.parentElement; if (container && !document.fullscreenElement) { container.requestFullscreen?.().catch(() => video.requestFullscreen?.()); } else { document.exitFullscreen?.(); } } }, GENERIC: { getVideo: () => { // 优先选择video.player元素 return document.querySelector('video.player') || findVideoInIframes() || document.querySelector('video, .video-player video, .video-js video, .player-container video'); }, adjustVolume: commonAdjustVolume, toggleFullscreen: (video) => toggleNativeFullscreen(video), } }; function findVideoInIframes() { const iframes = document.querySelectorAll('iframe'); for (const iframe of iframes) { try { const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document; return iframeDoc?.querySelector('video'); } catch {} } return null; } function toggleNativeFullscreen(video) { if (!video) return; try { if (document.fullscreenElement) { document.exitFullscreen(); } else { let elementToFullscreen = video; for (let i = 0; i < 2; i++) { elementToFullscreen = elementToFullscreen.parentElement || elementToFullscreen; } elementToFullscreen.requestFullscreen?.() || elementToFullscreen.webkitRequestFullscreen?.() || elementToFullscreen.msRequestFullscreen?.() || video.requestFullscreen?.() || video.webkitRequestFullscreen?.() || video.msRequestFullscreen?.(); } } catch (e) { console.error('Fullscreen error:', e); } } function simulateKeyPress(key) { document.dispatchEvent(new KeyboardEvent('keydown', {key, bubbles: true})); } function isInputElement(target) { return /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName) || target.isContentEditable; } function adjustRate(video, changeValue) { const state = getVideoState(video); const newRate = Math.max(0.1, Math.min(16, video.playbackRate + changeValue)); video.playbackRate = parseFloat(newRate.toFixed(1)); state.lastCustomRate = video.playbackRate; state.isDefaultRate = (video.playbackRate === 1.0); showVolume(video.playbackRate * 100); } function togglePlaybackRate(video) { const state = getVideoState(video); if (state.isDefaultRate) { video.playbackRate = state.lastCustomRate; state.isDefaultRate = false; } else { state.lastCustomRate = video.playbackRate; video.playbackRate = 1.0; state.isDefaultRate = true; } showVolume(video.playbackRate * 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 handleVideoWheel(e) { e.preventDefault(); e.stopPropagation(); const video = e.target; PLATFORM_HANDLERS[PLATFORM].adjustVolume(video, e.deltaY); } function handleTwitchWheel(e) { if (isInputElement(e.target)) return; const video = getVideoElement(); if (!video) return; const rect = video.getBoundingClientRect(); const inVideoArea = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom; if (inVideoArea) { e.preventDefault(); e.stopPropagation(); PLATFORM_HANDLERS.TWITCH.adjustVolume(video, e.deltaY); } } function handleKeyEvent(e) { if (isInputElement(e.target)) return; const video = getVideoElement(); const handler = PLATFORM_HANDLERS[PLATFORM]; // 优先处理自定义按键功能 if (e.code === 'Numpad7') { switch (CONFIG.key7Function) { case 1: // 长步进 video && (video.currentTime -= CONFIG.stepTimeLong); break; case 2: // 浏览器返回 history.back(); break; case 3: { // 上/下一个影片 const prevVideo = switchToPrevVideo(); if (prevVideo) prevVideo.play().catch(() => {}); break; } } e.preventDefault(); return; } if (e.code === 'Numpad9') { switch (CONFIG.key9Function) { case 1: // 长步进 video && (video.currentTime += CONFIG.stepTimeLong); break; case 2: // 浏览器前进 history.forward(); break; case 3: { // 上/下一个影片 const nextVideo = switchToNextVideo(); if (nextVideo) nextVideo.play().catch(() => {}); break; } } e.preventDefault(); return; } // 然后处理平台特殊按键 if (handler.specialKeys?.[e.code]) { handler.specialKeys[e.code](); e.preventDefault(); return; } // 最后处理其他通用按键 const actions = { 'Space': () => video && video[video.paused ? 'play' : 'pause'](), 'Numpad5': () => video && video[video.paused ? 'play' : 'pause'](), 'NumpadEnter': () => handler.toggleFullscreen(video), 'NumpadAdd': () => video && (video.currentTime += video.duration * 0.1), 'NumpadSubtract': () => video && (video.currentTime -= video.duration * 0.1), 'Numpad0': () => video && togglePlaybackRate(video), 'Numpad1': () => video && adjustRate(video, -0.1), 'Numpad3': () => video && adjustRate(video, 0.1), 'Numpad8': () => video && handler.adjustVolume(video, -CONFIG.stepVolume), 'Numpad2': () => video && handler.adjustVolume(video, CONFIG.stepVolume), 'Numpad4': () => video && (video.currentTime -= CONFIG.stepTime), 'Numpad6': () => video && (video.currentTime += CONFIG.stepTime) }; if (actions[e.code]) { actions[e.code](); e.preventDefault(); } } function bindVideoEvents() { if (PLATFORM === 'TWITCH') return; document.querySelectorAll('video').forEach(video => { if (!video.dataset.volumeBound) { video.addEventListener('wheel', handleVideoWheel, { passive: false }); video.dataset.volumeBound = 'true'; } }); } function init() { bindVideoEvents(); document.addEventListener('keydown', handleKeyEvent, true); if (PLATFORM === 'TWITCH') { document.addEventListener('wheel', handleTwitchWheel, { capture: true, passive: false }); } // 初始化视频元素列表 updateVideoElements(); // 监听DOM变化更新视频列表 new MutationObserver(() => { bindVideoEvents(); updateVideoElements(); // 确保当前激活视频仍然存在 if (activeVideoId && !videoElements.some(v => generateVideoId(v) === activeVideoId)) { activeVideoId = null; } }).observe(document.body, { childList: true, subtree: true }); } if (document.readyState !== 'loading') init(); else document.addEventListener('DOMContentLoaded', init); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址