// ==UserScript== // @name 自定义视频倍速播放 // @version 2.6 // @description 自定义视频播放速度 // @author DeepSeek // @match http://*/* // @match https://*/* // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // @namespace https://greasyfork.org/users/452911 // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 全局变量 let currentSpeed = parseFloat(GM_getValue('videoSpeed')) || 1; let controlBtn = null; let inputPanel = null; let speedInput = null; let initialized = false; let isProcessingClick = false; // 防止重复点击处理 // 防抖函数 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // 获取所有视频元素 function getAllVideoElements() { return document.querySelectorAll('video,[class*="player"] *'); } // 主初始化函数 function init() { if (initialized || document.fullscreenElement) return; let videos = getAllVideoElements(); if (videos.length === 0) return; initialized = true; // 应用速度到当前视频 applySpeedToVideos(videos, currentSpeed); // 创建控制按钮(如果不存在) if (!controlBtn || !controlBtn.parentNode) { createControlButton(); } } // 创建控制按钮 function createControlButton() { // 如果已存在,先移除 if (controlBtn && controlBtn.parentNode) { controlBtn.parentNode.removeChild(controlBtn); } controlBtn = document.createElement('div'); controlBtn.textContent = `倍速: ${currentSpeed}x`; controlBtn.title = '点击修改播放倍速'; controlBtn.style.cssText = ` position: fixed; right: 5px; top: 30px; padding: 8px 12px; background: rgba(0, 0, 0, 0.8); color: white; border: 1px solid #555; border-radius: 4px; cursor: pointer; z-index: 9999; font-size: 14px; font-family: Arial, sans-serif; user-select: none; min-width: 70px; text-align: center; pointer-events: auto; `; // 使用防抖防止快速多次点击 controlBtn.addEventListener('click', debounce(handleControlBtnClick, 300)); document.body.appendChild(controlBtn); } // 处理控制按钮点击 function handleControlBtnClick() { if (isProcessingClick) return; isProcessingClick = true; try { showSpeedInput(); } finally { setTimeout(() => { isProcessingClick = false; }, 100); } } // 显示速度输入框 function showSpeedInput() { // 如果已经有输入面板,先移除 if (inputPanel && inputPanel.parentNode) { hideSpeedInput(); return; } // 创建输入框面板 inputPanel = document.createElement('div'); inputPanel.style.cssText = ` position: fixed; right: 5px; top: 70px; background: rgba(0, 0, 0, 0.85); border: 1px solid #555; border-radius: 6px; z-index: 10000; font-size: 14px; font-family: Arial, sans-serif; user-select: none; min-width: 150px; padding: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); display: flex; flex-direction: column; gap: 10px; pointer-events: auto; `; // 创建标题 const title = document.createElement('div'); title.textContent = '设置播放倍速'; title.style.cssText = ` color: white; font-weight: bold; font-size: 14px; margin-bottom: 5px; text-align: center; `; // 创建输入框 speedInput = document.createElement('input'); speedInput.type = 'number'; speedInput.value = currentSpeed; speedInput.min = '0.1'; speedInput.max = '16'; speedInput.step = '0.1'; speedInput.style.cssText = ` width: 100%; padding: 8px 10px; border: 1px solid #666; border-radius: 4px; background: #333; color: white; font-size: 14px; outline: none; box-sizing: border-box; `; // 创建按钮容器 const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = ` display: flex; gap: 8px; justify-content: space-between; margin-top: 5px; `; // 创建取消按钮 const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.cssText = ` flex: 1; padding: 8px 0; background: #666; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: bold; transition: background 0.2s; `; // 创建确定按钮 const confirmBtn = document.createElement('button'); confirmBtn.textContent = '确定'; confirmBtn.style.cssText = ` flex: 1; padding: 8px 0; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: bold; transition: background 0.2s; `; // 按钮悬停效果 cancelBtn.addEventListener('mouseover', () => { cancelBtn.style.background = '#777'; }); cancelBtn.addEventListener('mouseout', () => { cancelBtn.style.background = '#666'; }); confirmBtn.addEventListener('mouseover', () => { confirmBtn.style.background = '#45a049'; }); confirmBtn.addEventListener('mouseout', () => { confirmBtn.style.background = '#4CAF50'; }); // 按钮事件处理 - 使用一次性事件 const cancelHandler = () => { hideSpeedInput(); cancelBtn.removeEventListener('click', cancelHandler); }; const confirmHandler = () => { applyNewSpeed(); confirmBtn.removeEventListener('click', confirmHandler); }; cancelBtn.addEventListener('click', cancelHandler); confirmBtn.addEventListener('click', confirmHandler); // 输入框回车和ESC事件 const inputKeyHandler = (e) => { if (e.key === 'Enter') { applyNewSpeed(); } else if (e.key === 'Escape') { hideSpeedInput(); } }; speedInput.addEventListener('keydown', inputKeyHandler); // 创建常用倍速按钮容器 const quickButtons = document.createElement('div'); quickButtons.style.cssText = ` display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; `; // 常用倍速按钮 const quickSpeeds = [0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 5.0]; quickSpeeds.forEach(speed => { const btn = document.createElement('button'); btn.textContent = `${speed}x`; btn.style.cssText = ` flex: 1; min-width: calc(33% - 4px); padding: 6px 0; background: ${Math.abs(speed - currentSpeed) < 0.01 ? '#2196F3' : '#555'}; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.2s; `; const clickHandler = () => { setSpeed(speed); hideSpeedInput(); btn.removeEventListener('click', clickHandler); }; btn.addEventListener('click', clickHandler); btn.addEventListener('mouseover', () => { if (Math.abs(speed - currentSpeed) >= 0.01) { btn.style.background = '#666'; } }); btn.addEventListener('mouseout', () => { if (Math.abs(speed - currentSpeed) >= 0.01) { btn.style.background = '#555'; } }); quickButtons.appendChild(btn); }); // 组装所有元素 buttonContainer.appendChild(cancelBtn); buttonContainer.appendChild(confirmBtn); inputPanel.appendChild(title); inputPanel.appendChild(speedInput); inputPanel.appendChild(buttonContainer); inputPanel.appendChild(quickButtons); document.body.appendChild(inputPanel); // 自动选中输入框内容并聚焦 setTimeout(() => { speedInput.focus(); speedInput.select(); }, 10); // 使用一次性事件监听器 const outsideClickHandler = (e) => { if (inputPanel && inputPanel.parentNode) { if (inputPanel.contains(e.target) || controlBtn.contains(e.target)) { return; } hideSpeedInput(); } }; // 延迟绑定,避免立即触发 setTimeout(() => { document.addEventListener('click', outsideClickHandler, true); }, 100); } // 隐藏输入框 function hideSpeedInput() { if (inputPanel && inputPanel.parentNode) { document.body.removeChild(inputPanel); inputPanel = null; speedInput = null; } // 移除所有相关的事件监听器 document.removeEventListener('click', handleOutsideClick, true); } // 应用新速度 function applyNewSpeed() { let newSpeed = parseFloat(speedInput.value); if (!isNaN(newSpeed) && newSpeed >= 0.1 && newSpeed <= 16) { setSpeed(newSpeed); hideSpeedInput(); } else { // 无效输入,恢复原值 speedInput.value = currentSpeed; alert('请输入有效的倍速值 (0.1 - 16)'); speedInput.focus(); speedInput.select(); } } // 设置速度(主函数) function setSpeed(newSpeed) { // 更新当前速度 currentSpeed = parseFloat(newSpeed.toFixed(2)); GM_setValue('videoSpeed', currentSpeed); // 更新控制按钮显示 if (controlBtn) { controlBtn.textContent = `倍速: ${currentSpeed}x`; } // 应用新速度到当前文档的所有视频 let videos = getAllVideoElements(); applySpeedToVideos(videos, currentSpeed); // 触发自定义事件(用于页面内通信) window.dispatchEvent(new CustomEvent('speedChanged', { detail: { speed: currentSpeed } })); } // 应用速度到视频元素 function applySpeedToVideos(videoElements, speed) { videoElements.forEach(element => { try { if (element.tagName === 'VIDEO') { element.playbackRate = speed; } else { let video = element.querySelector('video'); if (video) { video.playbackRate = speed; } else if ('playbackRate' in element) { element.playbackRate = speed; } } } catch (e) { // 忽略错误 } }); } // 优化检查新视频函数 function checkForNewVideos() { if (document.fullscreenElement || !document.body) return; let videos = getAllVideoElements(); if (videos.length > 0) { // 应用速度但不重复创建控制按钮 if (!controlBtn || !controlBtn.parentNode) { createControlButton(); } // 只对新视频或速度不同的视频应用速度 videos.forEach(video => { try { if (video.tagName === 'VIDEO' && Math.abs(video.playbackRate - currentSpeed) > 0.01) { video.playbackRate = currentSpeed; } } catch (e) { // 忽略错误 } }); } } // 节流函数 let throttleTimer; function throttleCheck() { if (throttleTimer) return; throttleTimer = setTimeout(() => { checkForNewVideos(); throttleTimer = null; }, 1500); // 增加延迟时间 } // 优化 MutationObserver 配置 let observer = null; function initObserver() { if (observer) { observer.disconnect(); } observer = new MutationObserver(debounce(throttleCheck, 500)); // 只监听子节点变化,减少监听范围 observer.observe(document.body, { childList: true, subtree: true }); } // 延迟初始化 function delayedInit() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(init, 1000); setTimeout(initObserver, 1500); }); } else { setTimeout(init, 1000); setTimeout(initObserver, 1500); } } // 初始运行 delayedInit(); // 添加全局函数以便其他脚本调用 window.setVideoSpeed = setSpeed; window.getCurrentVideoSpeed = () => currentSpeed; })();