Greasy Fork is available in English.
自定义视频播放速度
当前为
// ==UserScript==
// @name 自定义视频倍速播放
// @version 2.4
// @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;
// 获取所有视频元素
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();
}
// 尝试处理iframe中的视频
tryProcessIframeVideos();
}
// 创建控制按钮
function createControlButton() {
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;
`;
controlBtn.addEventListener('click', showSpeedInput);
document.body.appendChild(controlBtn);
}
// 显示速度输入框
function showSpeedInput() {
// 如果已经有输入面板,先移除
if (inputPanel && inputPanel.parentNode) {
inputPanel.parentNode.removeChild(inputPanel);
}
// 创建输入框面板
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;
`;
// 创建标题
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';
});
// 按钮事件处理
cancelBtn.addEventListener('click', hideSpeedInput);
confirmBtn.addEventListener('click', applyNewSpeed);
// 输入框回车和ESC事件
speedInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
applyNewSpeed();
} else if (e.key === 'Escape') {
hideSpeedInput();
}
});
// 创建常用倍速按钮容器
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];
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;
`;
btn.addEventListener('click', () => {
speedInput.value = speed;
// 立即应用快速倍速,不需要确认
setSpeed(speed);
hideSpeedInput();
});
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);
// 点击页面其他区域关闭输入框
document.addEventListener('click', handleOutsideClick, true);
}
// 处理外部点击
function handleOutsideClick(e) {
if (inputPanel && inputPanel.parentNode) {
// 如果点击的是输入框或按钮,不关闭
if (inputPanel.contains(e.target) || controlBtn.contains(e.target)) {
return;
}
hideSpeedInput();
}
}
// 隐藏输入框
function hideSpeedInput() {
if (inputPanel && inputPanel.parentNode) {
inputPanel.parentNode.removeChild(inputPanel);
inputPanel = 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 }
}));
// 尝试处理iframe中的视频
tryProcessIframeVideos(currentSpeed);
}
// 应用速度到视频元素
function applySpeedToVideos(videoElements, speed) {
videoElements.forEach(element => {
try {
// 如果是video元素
if (element.tagName === 'VIDEO') {
element.playbackRate = speed;
}
// 如果是其他元素,查找其中的video元素
else {
let video = element.querySelector('video');
if (video) {
video.playbackRate = speed;
} else {
// 如果元素本身有playbackRate属性,也尝试设置
if ('playbackRate' in element) {
element.playbackRate = speed;
}
}
}
} catch (e) {
// 忽略错误
}
});
}
// 尝试处理iframe中的视频
function tryProcessIframeVideos(speed = currentSpeed) {
document.querySelectorAll('iframe').forEach(iframe => {
try {
// 尝试访问iframe内容(仅同源iframe)
if (iframe.contentDocument) {
// 使用相同的选择器
let iframeVideos = iframe.contentDocument.querySelectorAll('video,[class*="player"] *');
applySpeedToVideos(iframeVideos, speed);
// 递归处理嵌套iframe
let nestedIframes = iframe.contentDocument.querySelectorAll('iframe');
nestedIframes.forEach(nestedIframe => {
tryProcessIframeElement(nestedIframe, speed);
});
}
} catch (e) {
// 跨域iframe会抛出安全错误,尝试使用postMessage
tryPostMessageToIframe(iframe, speed);
}
});
}
// 递归处理iframe元素
function tryProcessIframeElement(iframe, speed) {
try {
if (iframe.contentDocument) {
let iframeVideos = iframe.contentDocument.querySelectorAll('video,[class*="player"] *');
applySpeedToVideos(iframeVideos, speed);
}
} catch (e) {
// 忽略错误
}
}
// 尝试使用postMessage与iframe通信
function tryPostMessageToIframe(iframe, speed) {
try {
iframe.contentWindow.postMessage({
type: 'SET_VIDEO_SPEED',
speed: speed
}, '*');
} catch (e) {
// 忽略错误
}
}
// 监听来自iframe的消息(用于跨域通信)
window.addEventListener('message', (e) => {
if (e.data && e.data.type === 'SPEED_CHANGED' && e.data.speed) {
setSpeed(e.data.speed);
}
});
// 监听自定义事件(用于页面内通信)
window.addEventListener('speedChanged', (e) => {
if (e.detail && e.detail.speed) {
setSpeed(e.detail.speed);
}
});
// 轮询检查新视频(处理动态加载的视频)
function checkForNewVideos() {
if (document.fullscreenElement) return;
let videos = getAllVideoElements();
if (videos.length > 0) {
// 如果有视频但控制按钮不存在,初始化
if (!controlBtn || !controlBtn.parentNode) {
init();
} else {
// 否则只应用速度
applySpeedToVideos(videos, currentSpeed);
}
}
}
// 节流函数
let throttleTimer;
function throttleCheck() {
if (throttleTimer) return;
throttleTimer = setTimeout(() => {
checkForNewVideos();
throttleTimer = null;
}, 800);
}
// 事件监听器
window.addEventListener('scroll', throttleCheck);
window.addEventListener('load', throttleCheck);
document.addEventListener('DOMContentLoaded', throttleCheck);
// MutationObserver 监听DOM变化
const observer = new MutationObserver(throttleCheck);
observer.observe(document.body, {
childList: true,
subtree: true
});
// 初始运行
setTimeout(throttleCheck, 1000);
// 添加全局函数以便其他脚本调用
window.setVideoSpeed = setSpeed;
window.getCurrentVideoSpeed = () => currentSpeed;
})();