// ==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 // @downloadURL https://update.greasyfork.icu/scripts/529976/%E5%85%A8%E8%83%BD%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%99%A8%E9%80%9F%E5%BA%A6%E6%8E%A7%E5%88%B6%28%E6%9C%80%E5%A4%A716%E5%80%8D%E9%80%9F%29.user.js // @updateURL https://update.greasyfork.icu/scripts/529976/%E5%85%A8%E8%83%BD%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%99%A8%E9%80%9F%E5%BA%A6%E6%8E%A7%E5%88%B6%28%E6%9C%80%E5%A4%A716%E5%80%8D%E9%80%9F%29.meta.js // ==/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; } })();