Greasy Fork is available in English.
自定义视频播放速度
当前为
// ==UserScript==
// @name 自定义视频倍速播放
// @version 2.6
// @description 自定义视频播放速度
// @author DeepSeek
// @match http://*/*
// @match https://*/*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-idle
// @namespace http://greasyfork.icu/users/452911
// ==/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;
})();