// ==UserScript==
// @name 全能视频播放器速度控制(最大16倍速)
// @namespace http://tampermonkey.net/
// @version 3.8.1
// @description 支持【B站】【爱奇艺】【腾讯视频】【优酷】...等网站
// @author 不会起名
// @match *://*blog.csdn.net/*
// @match *://*download.csdn.net/*
// @match *://*c.pc.qq.com/middlem*
// @match *://*pan.baidu.com/disk/main*
// @match *://link.csdn.net/*
// @match *://link.zhihu.com/*
// @match *://browser.gwdang.com/*
// @match *://*www.jianshu.com/go-wild*
// @match *://*gitee.com/link*
// @match *://*juejin.cn/?target*
// @match *://www.aliyundrive.com/drive*
// @match *://www.alipan.com/drive/*
// @match *://*.youtube.com/watch?v=*
// @match *://support.qq.com/products*
// @match *://weibo.cn/sinaurl*
// @match *://afdian.net/link*
// @match *://*oschina.net/action/GoToLink*
// @match *://jump2.bdimg.com/safecheck*
// @match *://www.douban.com/link2/?url*
// @match *://link.17173.com*
// @match *://search.suning.com/*
// @match *://pan.quark.cn/*
// @match *://docs.qq.com/scenario/link*
// @match *://mail.qq.com/cgi-bin/readtemplate*
// @match *://cloud.tencent.com/developer/tools/blog-entry*
// @match *://link.uisdc.com/*
// @match *://*.tudou.com/listplay/*
// @match *://*.tudou.com/albumplay/*
// @match *://*.tudou.com/programs/view/*
// @match *://*.tudou.com/v*
// @match *://*.mgtv.com/b/*
// @match *://film.sohu.com/album/*
// @match *://tv.sohu.com/v/*
// @match *://*.acfun.cn/v/*
// @match *://*.bilibili.com/video/*
// @match *://*.bilibili.com/anime/*
// @match *://*.bilibili.com/bangumi/play/*
// @match *://*.pptv.com/show/*
// @match *://*.baofeng.com/play/*
// @match *://*.wasu.cn/Play/show*
// @match *://v.yinyuetai.com/video/*
// @match *://v.yinyuetai.com/playlist/*
// @match *://*.wasu.cn/Play/show/*
// @match *://music.taihe.com/song*
// @match *://music.163.com/song*
// @match *://music.163.com/m/song*
// @match *://y.qq.com/*
// @match *://*.kugou.com/*
// @match *://*.kuwo.cn/*
// @match *://*.xiami.com/*
// @match *://music.taihe.com/*
// @match *://*.1ting.com/player*
// @match *://www.qingting.fm/*
// @match *://www.lizhi.fm/*
// @match *://music.migu.cn/*
// @match *://www.shangxueba.com/ask/*.html
// @match *://www.ximalaya.com/*
// @match *://www.shangxueba.com/ask/*.html
// @match *://pan.baidu.com/disk/home*
// @match *://yun.baidu.com/disk/home*
// @match *://pan.baidu.com/s/*
// @match *://yun.baidu.com/s/*
// @match *://pan.baidu.com/share/link*
// @match *://yun.baidu.com/share/link*
// @match *://wenku.baidu.com/view/*
// @match *://settings.wandhi.com/*
// @match *://m.youku.com/v*
// @match *://m.youku.com/a*
// @match *://v.youku.com/v_*
// @match *://v.youku.com/pad_show*
// @match *://*.iqiyi.com/v_*
// @match *://*.iqiyi.com/w_*
// @match *://*.iqiyi.com/a_*
// @match *://*.iqiyi.com/adv*
// @match *://*.iq.com/play/*
// @match *://*.le.com/ptv/vplay/*
// @match *://v.qq.com/x/cover/*
// @match *://v.qq.com/x/page/*
// @match *://v.qq.com/*play*
// @match *://v.qq.com/cover*
// @match *://c.pc.qq.com/ios*
// @match *://www.v2ex.com/t/*
// @match *://*.nodeseek.com/jump*
// @match *://*.zhihu.com/question*
// @match *://www.baidu.com/*
// @match *://www.google.com/*
// @match *://www.sogou.com/*
// @match *://www.so.com/s*
// @match *://cn.bing.com/search*
// @match *://sspai.com/link*
// @match *://*.kdocs.cn/office/link*
// @match *://ispacesoft.com/*.html
// @match *://tv.wandhi.com/go.html*
// @match *://tv.wandhi.com/check.html
// @match *://*.xiaohongshu.com/explore*
// @match *://www.yuque.com/r/goto*
// @match *://blog.51cto.com/transfer*
// @match *://r.wjx.com/redirect.aspx*
// @match *://www.infoq.cn/link*
// @icon https://www.bilibili.com/favicon.ico
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ================
// 全局CSS变量
// ================
document.documentElement.style.setProperty('--greyLightText', '#9baacf');
document.documentElement.style.setProperty('--greyLightBg', '#e4ebf5');
document.documentElement.style.setProperty('--greyLightShadow1', '#c8d0e7');
document.documentElement.style.setProperty('--greyLightShadow2', '#fff');
document.documentElement.style.setProperty('--greyDarkText', 'white');
document.documentElement.style.setProperty('--greyDarkBg', '#696969');
document.documentElement.style.setProperty('--greyDarkShadow1', '#595959');
document.documentElement.style.setProperty('--greyDarkShadow2', '#797979');
// ================
// 配置和常量
// ================
const CONFIG = {
pos: GM_getValue('controlPos', { x: 20, y: 20 }),
isCollapsed: GM_getValue('isCollapsed', false),
theme: GM_getValue('theme', 'light'),
speed: {
min: '0.10',
max: '16',
step: '0.05',
value: '1',
}
};
const THEMES = {
dark: {
bg: 'var(--greyDarkBg)',
text: 'var(--greyDarkText)',
border: '#666',
buttonBg: '#555',
buttonText: 'var(--greyDarkText)',
inputBg: '#333',
boxShadow: '3px 3px 6px var(--greyDarkShadow1), -2px -2px 5px var(--greyDarkShadow2)',
clickBoxShadow: 'inset 2px 2px 5px var(--greyDarkShadow1), inset -2px -2px 5px var(--greyDarkShadow2) !important',
rangeSlider1: 'white',
rangeSlider2: '#b1b1b1',
},
light: {
bg: 'var(--greyLightBg)',
text: 'var(--greyLightText)',
border: '#ddd',
buttonBg: '#eee',
buttonText: 'var(--greyLightText)',
inputBg: '#fff',
boxShadow: '3px 3px 6px var(--greyLightShadow1), -2px -2px 5px var(--greyLightShadow2)',
clickBoxShadow: 'inset 2px 2px 5px var(--greyLightShadow1), inset -2px -2px 5px var(--greyLightShadow2) !important',
rangeSlider1: 'white',
rangeSlider2: 'var(--greyLightText)',
},
};
// ================
// 全局变量
// ================
let video = null;
let isDragging = false;
let startX, startY, initLeft, initTop;
// ================
// DOM 元素
// ================
const controls = createControls();
const header = createHeader();
const speedDisplay = document.createElement('span');
const speedSlider = document.createElement('input');
const numInput = document.createElement('input');
const content = createContent();
// ================
// 动态创建 CSS 类
// ================
const style123 = document.createElement('style');
style123.textContent = '#bili-speed-control .lightBtn:active{box-shadow:' + THEMES[CONFIG.theme].clickBoxShadow + '}#bili-speed-control .darkBtn:active{box-shadow:' + THEMES[CONFIG.theme].clickBoxShadow + '}';
document.head.appendChild(style123);
const styleRange = document.createElement('style');
styleRange.textContent = `
#bili-speed-control {
position: fixed;
z-index: 9999;
border-radius: 5px;
cursor: move;
user-select: none;
transition: width 0.3s ease;
}
#bili-speed-control .BSC_header {
display: flex;
justify-content: space-between;
align-items: center;
}
#bili-speed-control .btnConfig {
padding: 2px 8px;
border-radius: 3px;
background: transparent;
border: none;
cursor: pointer;
}
#bili-speed-control .lightBtn {
}
#bili-speed-control .darkBtn {
}
#bili-speed-control .slider {
--slider-width: 100%;
--slider-height: 6px;
--slider-border-radius: 999px;
--level-transition-duration: .1s;
}
#bili-speed-control .slider {
display: flex;
align-items: center;
cursor: pointer;
}
#bili-speed-control .slider .level {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: var(--slider-width);
height: var(--slider-height);
background: var(--slider-bg);
overflow: hidden;
border-radius: var(--slider-border-radius);
-webkit-transition: height var(--level-transition-duration);
-o-transition: height var(--level-transition-duration);
transition: height var(--level-transition-duration);
cursor: pointer;
}
#bili-speed-control .level::-webkit-slider-thumb {
-webkit-appearance: none;
width: 0;
height: 0;
-webkit-box-shadow: -200px 0 0 200px var(--level-color);
box-shadow: -200px 0 0 200px var(--level-color);
}
#bili-speed-control .slider:hover .level {
height: calc(var(--slider-height) * 2);
}
#bili-speed-control .numInputSpeed {
width: 50px;
margin-left: 10px;
padding: 3px 6px;
border-radius: 4px;
border: none;
background: transparent;
}
`;
document.head.appendChild(styleRange);
// ================
// 主初始化流程
// ================
initializeControls();
function createControls() {
const el = document.createElement('div');
el.id = 'bili-speed-control';
Object.assign(el.style, {
padding: CONFIG.isCollapsed ? '8px' : '10px',
width: CONFIG.isCollapsed ? '150px' : '200px',
});
return el;
}
function createHeader() {
const header = document.createElement('div');
header.classList.add('BSC_header');
header.style.marginBottom = CONFIG.isCollapsed ? '0' : '10px';
const title = document.createElement('span');
title.textContent = '🎚️ 播放控制';
const btnContainer = document.createElement('div');
const toggleBtn = createButton(
CONFIG.isCollapsed ? '▶' : '▼',
{
marginRight: '5px',
boxShadow: THEMES[CONFIG.theme].boxShadow,
},
() => toggleCollapse()
);
const themeBtn = createButton(CONFIG.theme === 'dark' ? '🌞' : '🌙', { boxShadow: THEMES[CONFIG.theme].boxShadow }, () => toggleTheme());
btnContainer.append(toggleBtn, themeBtn);
header.append(title, btnContainer);
return header;
}
function createContent() {
const content = document.createElement('div');
const content2 = document.createElement('div');
Object.assign(content.style, {
overflow: 'hidden',
transition: 'all 0.3s ease',
opacity: CONFIG.isCollapsed ? '0' : '1',
maxHeight: CONFIG.isCollapsed ? '0px' : '200px',
});
// 预设按钮
const presetContainer = document.createElement('div');
presetContainer.style.marginBottom = '10px';
[0.5, 0.65, 0.85, 1.0, 1.15, 1.25].forEach((speed) => {
const btn = createButton(
`${speed}x`,
{
margin: '3px',
width: CONFIG.isCollapsed ? '40px' : '60px',
transition: 'width 0.3s ease',
},
() => syncInputs(speed)
);
presetContainer.appendChild(btn);
});
// 速度控制组件
speedDisplay.textContent = '当前速度:1x';
speedSlider.type = 'range';
Object.assign(speedSlider, CONFIG.speed);
speedSlider.classList.add('level');
numInput.type = 'number';
Object.assign(numInput, CONFIG.speed);
numInput.classList.add('numInputSpeed')
content.append(presetContainer, speedDisplay);
content2.append(speedSlider, numInput);
content2.classList.add('slider');
content.append(content2);
return content;
}
function createButton(text, style, clickHandler) {
const btn = document.createElement('button');
btn.textContent = text;
btn.classList.add('btnConfig')
Object.assign(btn.style, {
...style,
});
btn.classList.add(CONFIG.theme + 'Btn');
btn.addEventListener('click', clickHandler);
return btn;
}
// ================
// 核心功能
// ================
function initializeControls() {
controls.style.left = `${CONFIG.pos.x}px`;
controls.style.top = `${CONFIG.pos.y}px`;
controls.append(header, content);
document.body.appendChild(controls);
applyTheme();
setupEventListeners();
}
function applyTheme() {
const theme = THEMES[CONFIG.theme];
document.documentElement.style.setProperty('--level-color', theme.rangeSlider1);
document.documentElement.style.setProperty('--slider-bg', theme.rangeSlider2);
Object.assign(controls.style, {
background: theme.bg,
color: theme.text,
border: `1px solid ${theme.border}`,
});
document.querySelectorAll('#bili-speed-control button').forEach((btn) => {
Object.assign(btn.style, {
color: theme.buttonText,
boxShadow: THEMES[CONFIG.theme].boxShadow,
});
});
Object.assign(numInput.style, {
boxShadow: THEMES[CONFIG.theme].clickBoxShadow,
});
}
function setupEventListeners() {
// 拖拽
header.addEventListener('mousedown', startDrag);
document.addEventListener('mousemove', handleDrag);
document.addEventListener('mouseup', endDrag);
// 速度控制
speedSlider.addEventListener('input', (e) => syncInputs(e.target.value));
numInput.addEventListener('change', handleNumberInput);
// 快捷键
document.addEventListener('keydown', handleKeyboardShortcuts);
// 视频检测
setTimeout(updateVideoElement, 500);
}
// ================
// 功能实现
// ================
function toggleCollapse() {
CONFIG.isCollapsed = !CONFIG.isCollapsed;
// 宽度切换
controls.style.width = CONFIG.isCollapsed ? '150px' : '200px';
controls.style.padding = CONFIG.isCollapsed ? '8px' : '10px';
// 内容区域切换
content.style.maxHeight = CONFIG.isCollapsed ? '0px' : '200px';
content.style.opacity = CONFIG.isCollapsed ? '0' : '1';
// 按钮尺寸切换
content.querySelectorAll('button').forEach(btn => {
btn.style.width = CONFIG.isCollapsed ? '40px' : '60px';
});
// 标题栏间距调整
header.style.marginBottom = CONFIG.isCollapsed ? '0' : '10px';
// 更新按钮图标
header.querySelector('button').textContent = CONFIG.isCollapsed ? '▶' : '▼';
GM_setValue('isCollapsed', CONFIG.isCollapsed);
}
function toggleTheme() {
CONFIG.theme = CONFIG.theme === 'dark' ? 'light' : 'dark';
const themeBtn = header.querySelectorAll('button')[1];
themeBtn.textContent = CONFIG.theme === 'dark' ? '🌞' : '🌙';
applyTheme();
GM_setValue('theme', CONFIG.theme);
}
function startDrag(e) {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initLeft = parseFloat(controls.style.left);
initTop = parseFloat(controls.style.top);
controls.style.cursor = 'grabbing';
}
function handleDrag(e) {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
controls.style.left = `${initLeft + dx}px`;
controls.style.top = `${initTop + dy}px`;
}
function endDrag() {
if (!isDragging) return;
isDragging = false;
controls.style.cursor = 'move';
GM_setValue('controlPos', {
x: parseFloat(controls.style.left),
y: parseFloat(controls.style.top),
});
}
function handleNumberInput(e) {
const val = Math.min(16, Math.max(0.1, e.target.value));
syncInputs(val);
}
function handleKeyboardShortcuts(e) {
if (e.altKey) {
const current = parseFloat(speedSlider.value);
if (current - 0.05 < 0.1 || current + 0.05 > 16) return;
if (e.key === 'ArrowUp') syncInputs(current + 0.05);
if (e.key === 'ArrowDown') syncInputs(current - 0.05);
if (e.key === 'r') syncInputs(1.0);
}
}
function updateVideoElement() {
video = document.querySelector('video');
}
function syncInputs(value) {
const speed = parseFloat(value).toFixed(2);
speedSlider.value = speed;
numInput.value = speed;
speedDisplay.textContent = `当前速度:${speed}x`;
if (video) video.playbackRate = speed;
}
})();