// ==UserScript==
// @name 控制视频快进倍速及音量调节脚本(含拖动进度条)
// @namespace http://tampermonkey.net/
// @version 1.1.1
// @description 1.滚轮控制进度;2.Shift+滚轮调音量;3.Ctrl 临时2倍速;4.左键拖动调节进度条
// @author lhr3572651322
// @license MIT
// @match *://*.bilibili.com/video/*
// @match *://*.youtube.com/watch*
// @match *://*.youku.com/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
/* ---------- 1. 样式常量(与之前相同,略) ---------- */
const styles = {
base: `
position: fixed;
color: white;
z-index: 2147483647;
transition: all 0.3s ease;
`,
tip: `
left: 50%;
top: 20px;
transform: translateX(-50%);
background: rgba(33, 150, 243, 0.9);
padding: 10px 20px;
border-radius: 4px;
font-size: 14px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
opacity: 0;
text-align: center;
display: flex;
align-items: center;
gap: 12px;
`,
neverBtn: `
background: rgba(255,255,255,0.2);
border: none;
color: white;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
`,
guide: `
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
background: rgba(0, 0, 0, 0.8);
padding: 15px 20px;
border-radius: 8px;
font-size: 14px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
opacity: 0;
border-left: 3px solid #2196F3;
line-height: 1.8;
pointer-events: none;
text-align: left;
`,
volumeIndicator: `
position: absolute;
top: 60px;
right: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 5px 10px;
border-radius: 4px;
font-size: 14px;
pointer-events: none;
opacity: 0;
`,
// 新增:拖动进度条提示
dragBar: `
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.6);
padding: 6px 12px;
border-radius: 4px;
font-size: 14px;
pointer-events: none;
opacity: 0;
z-index: 2147483647;
`
};
/* ---------- 2. 首次加载提示(略,同前) ---------- */
if (!localStorage.getItem('skipShiftGuide')) {
const tip = document.createElement('div');
tip.style.cssText = styles.base + styles.tip;
tip.innerHTML = `
<span>按住 Shift 键可随时查看操作指引</span>
<button id="neverShowAgain" style="${styles.neverBtn}">不再提示</button>
`;
document.body.appendChild(tip);
setTimeout(() => tip.style.opacity = 1, 100);
tip.querySelector('#neverShowAgain').addEventListener('click', () => {
localStorage.setItem('skipShiftGuide', '1');
tip.style.opacity = 0;
setTimeout(() => tip.remove(), 300);
});
setTimeout(() => {
tip.style.opacity = 0;
setTimeout(() => tip.remove(), 300);
}, 5000);
}
/* ---------- 3. 轮询找视频(略,同前) ---------- */
const checkVideo = setInterval(() => {
const hostname = window.location.hostname;
const video =
hostname.includes('missav.com')
? document.querySelector('.plyr--video video')
: hostname.includes('jable.tv')
? document.querySelector('#player-container video, #player_3 video, .plyr--video video')
: document.querySelector('.html5-main-video, video, .youku-player video');
if (video?.readyState >= 2) {
clearInterval(checkVideo);
initVideoControl(video);
}
}, 1000);
/* ---------- 4. 初始化视频控制 ---------- */
function initVideoControl(video) {
if (video.dataset.controlInitialized) return;
video.dataset.controlInitialized = 'true';
const hostname = window.location.hostname;
const isYouku = hostname.includes('youku.com');
const isMissav = hostname.includes('missav.com');
const isJable = hostname.includes('jable.tv');
const container =
isMissav || isJable
? video.closest('.plyr--video') || video.closest('#player-container') || video.parentElement
: isYouku
? document.querySelector('.youku-player')
: video.closest('#movie_player') || video.parentElement;
if (!container) return;
let originalRate = video.playbackRate;
/* ---------- 5. 滚轮事件(略,同前) ---------- */
const wheelHandler = e => {
e.preventDefault();
e.stopPropagation();
if (e.shiftKey) {
video.volume = Math.min(1, Math.max(0,
video.volume + (e.deltaY < 0 ? 0.05 : -0.05)
));
showVolumeIndicator(video, container);
} else {
const step = e.deltaY < 0 ? -2 : 2;
video.currentTime = Math.min(video.duration, Math.max(0, video.currentTime + step));
}
return false;
};
const elements = (isMissav || isJable) ? [container, video] : [isYouku ? container : video];
elements.forEach(el => {
el.removeEventListener('wheel', wheelHandler, { passive: false, capture: true });
el.addEventListener('wheel', wheelHandler, { passive: false, capture: true });
});
/* ---------- 6. Ctrl 临时 2 倍速(略,同前) ---------- */
document.addEventListener('keydown', e => {
if (e.key === 'Control' && !e.repeat) {
originalRate = video.playbackRate;
if (video.paused) video.play();
video.playbackRate = 2;
}
});
document.addEventListener('keyup', e => {
if (e.key === 'Control') video.playbackRate = originalRate;
});
/* ---------- 8. 指引框 & 音量提示(略,同前) ---------- */
addGuideBox(video);
}
/* ---------- 9. 工具函数 ---------- */
function createElement(className, style) {
const element = document.createElement('div');
element.className = className;
element.style.cssText = styles.base + style;
return element;
}
function showVolumeIndicator(video, container) {
let indicator = container.querySelector('.volume-indicator');
if (!indicator) {
indicator = createElement('volume-indicator', styles.volumeIndicator);
container.appendChild(indicator);
}
indicator.textContent = `音量: ${Math.round(video.volume * 100)}%`;
indicator.style.opacity = '1';
clearTimeout(indicator.fadeTimeout);
indicator.fadeTimeout = setTimeout(() => indicator.style.opacity = '0', 2000);
}
function addGuideBox(video) {
const container = video.closest('.plyr--video') || video.parentElement;
const guideBox = createElement('video-control-guide', styles.guide);
const updateGuide = () => {
guideBox.innerHTML = `
<div style="margin-bottom: 5px;">按住 Shift:显示此提示</div>
<div style="margin-bottom: 5px;">🖱️滚轮:快进/快退</div>
<div style="margin-bottom: 5px;">Shift+滚轮:调节音量(5%)</div>
<div style="margin-bottom: 5px;">按住 Ctrl:临时 2 倍速</div>
<div style="margin-bottom: 5px;">左键拖动视频:调节进度</div>
<div style="color: #2196F3;">音量: ${Math.round(video.volume * 100)}%</div>
`;
};
container.appendChild(guideBox);
video.addEventListener('volumechange', updateGuide);
updateGuide();
document.addEventListener('keydown', e => {
if (e.key === 'Shift') {
e.preventDefault();
guideBox.style.opacity = '1';
guideBox.style.transform = 'translateY(-50%)';
}
});
document.addEventListener('keyup', e => {
if (e.key === 'Shift') {
guideBox.style.opacity = '0';
guideBox.style.transform = 'translateY(-50%) translateX(-20px)';
}
});
/* ---------- 7. 左键拖动进度条(比例版) ---------- */
let isDragging = false;
let startX = 0;
let startTime = 0;
let videoWidth = 0; // 缓存一次视频宽度,防止拖动中实时取抖动
const dragBar = createElement('drag-bar', styles.dragBar);
container.appendChild(dragBar);
// 倍率:1倍
const DRAG_SENSITIVITY = 1;
container.addEventListener('mousedown', e => {
if (e.button !== 0) return;
isDragging = true;
startX = e.clientX;
startTime = video.currentTime;
// 实时拿一下视频宽度(拖动过程中不变)
const rect = video.getBoundingClientRect();
videoWidth = rect.width || 1; // 防止 0
dragBar.style.opacity = '1';
e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!isDragging) return;
const deltaX = e.clientX - startX;
// 拖动比例:[-∞, +∞]
const ratio = deltaX / videoWidth;
const deltaTime = ratio * video.duration / DRAG_SENSITIVITY;
video.currentTime = Math.min(video.duration, Math.max(0, startTime + deltaTime));
// 实时显示百分比
const percent = ((video.currentTime / video.duration) * 100).toFixed(1);
dragBar.textContent = `${percent}%`;
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
dragBar.style.opacity = '0';
});
}
})();