// ==UserScript== // @name B站播放速度控制最终版 // @namespace http://tampermonkey.net/ // @version 2.1 // @description 支持折叠宽度变化、主题切换和速度预设的播放控制 // @author YourName // @match https://www.bilibili.com/video/* // @icon https://www.bilibili.com/favicon.ico // @grant GM_setValue // @grant GM_getValue // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // ================ // 配置和常量 // ================ const CONFIG = { pos: GM_getValue('controlPos', {x: 20, y: 20}), isCollapsed: GM_getValue('isCollapsed', false), theme: GM_getValue('theme', 'dark') }; const THEMES = { dark: { bg: 'rgba(0,0,0,0.7)', text: 'white', border: '#666', buttonBg: '#555', buttonText: '#fff', inputBg: '#333' }, light: { bg: 'rgba(255,255,255,0.9)', text: '#333', border: '#ddd', buttonBg: '#eee', buttonText: '#333', inputBg: '#fff' } }; // ================ // 全局变量 // ================ let video = null; let isDragging = false; let startX, startY, initLeft, initTop; // ================ // DOM 元素 // ================ const controls = createControls(); const header = createHeader(); const content = createContent(); // ================ // 主初始化流程 // ================ initializeControls(); function createControls() { const el = document.createElement('div'); el.id = 'bili-speed-control'; Object.assign(el.style, { position: 'fixed', zIndex: '9999', padding: CONFIG.isCollapsed ? '8px' : '10px', borderRadius: '5px', cursor: 'move', userSelect: 'none', transition: 'all 0.3s ease', width: CONFIG.isCollapsed ? '150px' : '200px' }); return el; } function createHeader() { const header = document.createElement('div'); header.style.display = 'flex'; header.style.justifyContent = 'space-between'; header.style.alignItems = 'center'; 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' }, () => toggleCollapse() ); const themeBtn = createButton( CONFIG.theme === 'dark' ? '🌞' : '🌙', {}, () => 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', marginTop: CONFIG.isCollapsed ? '0' : '10px' }); // 预设按钮 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); }); // 速度控制组件 const speedDisplay = document.createElement('span'); speedDisplay.style.marginRight = '10px'; speedDisplay.textContent = '当前速度:1x'; const speedSlider = document.createElement('input'); speedSlider.type = 'range'; Object.assign(speedSlider, { min: '0.5', max: '10', step: '0.05', value: '1' }); Object.assign(speedSlider.style, { width: '100%', verticalAlign: 'middle', cursor: 'pointer' }); const numInput = document.createElement('input'); numInput.type = 'number'; Object.assign(numInput, { min: '0.5', max: '10', step: '0.05', value: '1' }); Object.assign(numInput.style, { width: '60px', marginLeft: '10px', padding: '3px', borderRadius: '3px', border: '1px solid' }); content.append(presetContainer, speedDisplay); content2.append(speedSlider, numInput) content2.style.display = 'flex'; content.append(content2) return content; } function createButton(text, style, clickHandler) { const btn = document.createElement('button'); btn.textContent = text; Object.assign(btn.style, { padding: '2px 8px', borderRadius: '3px', cursor: 'pointer', ...style }); 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]; 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, { background: theme.buttonBg, color: theme.buttonText, borderColor: theme.border }); }); const numInput = content.querySelector('input[type="number"]'); Object.assign(numInput.style, { background: theme.inputBg, color: theme.text, borderColor: theme.border }); } function setupEventListeners() { // 拖拽 header.addEventListener('mousedown', startDrag); document.addEventListener('mousemove', handleDrag); document.addEventListener('mouseup', endDrag); // 速度控制 const speedSlider = content.querySelector('input[type="range"]'); const numInput = content.querySelector('input[type="number"]'); speedSlider.addEventListener('input', e => syncInputs(e.target.value)); numInput.addEventListener('change', handleNumberInput); // 快捷键 document.addEventListener('keydown', handleKeyboardShortcuts); // 视频检测 setInterval(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.style.marginTop = CONFIG.isCollapsed ? '0' : '10px'; // 按钮尺寸切换 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'; controls.style.transition = 'none'; } 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'; controls.style.transition = 'all 0.3s ease'; GM_setValue('controlPos', { x: parseFloat(controls.style.left), y: parseFloat(controls.style.top) }); } function handleNumberInput(e) { const val = Math.min(10, Math.max(0.5, e.target.value)); syncInputs(val.toFixed(2)); } function handleKeyboardShortcuts(e) { if (e.altKey) { const slider = content.querySelector('input[type="range"]'); const current = parseFloat(slider.value); if (e.key === 'ArrowUp') syncInputs((current + 0.05).toFixed(2)); if (e.key === 'ArrowDown') syncInputs((current - 0.05).toFixed(2)); if (e.key === 'r') syncInputs(1.00); } } function updateVideoElement() { video = document.querySelector('video'); if (video) { const slider = content.querySelector('input[type="range"]'); video.playbackRate = slider.value; syncInputs(video.playbackRate); } } function syncInputs(value) { const speed = parseFloat(value).toFixed(2); const speedDisplay = content.querySelector('span'); const slider = content.querySelector('input[type="range"]'); const numInput = content.querySelector('input[type="number"]'); slider.value = speed; numInput.value = speed; speedDisplay.textContent = `当前速度:${speed}x`; if (video) video.playbackRate = speed; } })();