视频加速
// ==UserScript==
// @name 视频加速
// @namespace scriptcat
// @version 1.02
// @description Control video playback speed on any website, including live streams
// @author Your Name
// @match *://*.douyin.com/*
// @match *://*.bilibili.com/*
// @match *://*.qq.com/*
// @match *://*.youku.com/*
// @match *://*.iqiyi.com/*
// @match *://v.kuaishou.com/*
// @match *://*.mgtv.com/*
// @match *://*.youtube.com/*
// @grant window.onurlchange
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-start
// ==/UserScript==
const css = `
#speedController {
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 9999999;
font-family: Arial, sans-serif;
user-select: none;
display: flex;
flex-direction: column;
gap: 5px;
}
#speedController button {
background: #4CAF50;
border: none;
color: white;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
margin: 2px;
min-width: 50px;
}
#speedController button:hover {
background: #45a049;
}
#speedValue {
text-align: center;
font-size: 16px;
margin: 5px 0;
}
.speed-row {
display: flex;
justify-content: center;
gap: 5px;
}
`;
(function() {
'use strict';
let currentSpeed = 1.0;
let originalSetPlaybackRate;
let originalPlaybackRate;
// 在脚本开始时保存原始的 playbackRate 设置函数
if (HTMLMediaElement.prototype) {
originalSetPlaybackRate = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate').set;
originalPlaybackRate = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate').get;
}
// 劫持所有视频元素的 playbackRate 属性
function hijackPlaybackRate() {
try {
Object.defineProperty(HTMLMediaElement.prototype, 'playbackRate', {
get: function() {
return currentSpeed;
},
set: function(speed) {
currentSpeed = speed;
try {
originalSetPlaybackRate.call(this, speed);
} catch (e) {
console.error('Error in playbackRate setter:', e);
}
},
configurable: true
});
} catch (e) {
console.error('Error hijacking playbackRate:', e);
}
}
// Speed control function
function adjustSpeed(speed, decrease = false, isIncrement = false) {
try {
if (isIncrement) {
currentSpeed = Math.min(16, currentSpeed + speed);
} else if (decrease) {
currentSpeed = Math.max(0.1, currentSpeed - speed);
} else {
currentSpeed = Math.min(16, speed);
}
// 更新所有视频的播放速度
const videos = document.getElementsByTagName('video');
Array.from(videos).forEach(video => {
try {
// 直接设置视频元素的速度
video.defaultPlaybackRate = currentSpeed;
originalSetPlaybackRate.call(video, currentSpeed);
// 尝试使用 webkitPlaybackRate (针对一些特殊浏览器)
if (video.webkitPlaybackRate !== undefined) {
video.webkitPlaybackRate = currentSpeed;
}
// 强制刷新播放状态
const isPlaying = !video.paused;
if (isPlaying) {
video.pause();
video.play().catch(() => {});
}
} catch (e) {
console.error('Error setting video speed:', e);
}
});
// 更新显示的速度值
const speedValue = document.getElementById('speedValue');
if (speedValue) {
speedValue.textContent = currentSpeed.toFixed(1) + 'x';
}
// 保存当前速度设置
try {
GM_setValue('lastSpeed', currentSpeed);
} catch (e) {
console.error('Error saving speed:', e);
}
} catch (e) {
console.error('Error in adjustSpeed:', e);
}
}
// Create control panel
function createSpeedController() {
const controller = document.createElement('div');
controller.id = 'speedController';
controller.innerHTML = `
<div id="speedValue">${currentSpeed.toFixed(1)}x</div>
<div class="speed-row">
<button id="speed05">0.5x</button>
<button id="speed10">1x</button>
<button id="speed15">1.5x</button>
<button id="speed20">2x</button>
</div>
<div class="speed-row">
<button id="speed30">3x</button>
<button id="speed50">5x</button>
<button id="speed80">8x</button>
<button id="speed160">16x</button>
</div>
<div class="speed-row">
<button id="speedDown">-0.1</button>
<button id="speedUp">+0.1</button>
</div>
`;
// 事件委托处理按钮点击
controller.addEventListener('click', function(e) {
const target = e.target;
if (target.tagName === 'BUTTON') {
e.preventDefault();
e.stopPropagation();
switch(target.id) {
case 'speed05':
adjustSpeed(0.5);
break;
case 'speed10':
adjustSpeed(1.0);
break;
case 'speed15':
adjustSpeed(1.5);
break;
case 'speed20':
adjustSpeed(2.0);
break;
case 'speed30':
adjustSpeed(3.0);
break;
case 'speed50':
adjustSpeed(5.0);
break;
case 'speed80':
adjustSpeed(8.0);
break;
case 'speed160':
adjustSpeed(16.0);
break;
case 'speedDown':
adjustSpeed(0.1, true);
break;
case 'speedUp':
adjustSpeed(0.1, false, true);
break;
}
}
});
document.body.appendChild(controller);
return controller;
}
// Initialize controller with drag functionality
function initializeController() {
const controller = createSpeedController();
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
controller.addEventListener("mousedown", function(e) {
if (e.target === controller || e.target.id === 'speedValue') {
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
isDragging = true;
}
});
document.addEventListener("mousemove", function(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
controller.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;
}
});
document.addEventListener("mouseup", function() {
initialX = currentX;
initialY = currentY;
isDragging = false;
});
}
// 添加样式
function addStyles() {
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
}
// 在脚本开头添加 URL 变化监听
if (window.onurlchange === null) {
window.addEventListener('urlchange', () => {
// URL变化时重新应用速度设置
setTimeout(applySpeedToAllVideos, 1000);
});
}
// 提取视频速度应用逻辑为单独函数
function applySpeedToAllVideos() {
const videos = document.getElementsByTagName('video');
Array.from(videos).forEach(video => {
try {
// 直接设置视频元素的速度
video.defaultPlaybackRate = currentSpeed;
originalSetPlaybackRate.call(video, currentSpeed);
// 尝试使用 webkitPlaybackRate
if (video.webkitPlaybackRate !== undefined) {
video.webkitPlaybackRate = currentSpeed;
}
} catch (e) {
console.error('Error setting video speed:', e);
}
});
}
// Initialize everything
function initialize() {
// 检查当前网站是否有视频元素
function checkForVideos() {
const videos = document.getElementsByTagName('video');
if (videos.length > 0) {
// 只有在找到视频元素时才初始化控制器
try {
currentSpeed = GM_getValue('lastSpeed', 1.0);
} catch (e) {
currentSpeed = 1.0;
console.error('Error getting last speed:', e);
}
hijackPlaybackRate();
// 确保控制器只被创建一次
if (!document.getElementById('speedController')) {
addStyles();
initializeController();
}
}
}
// 初始检查
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', checkForVideos);
} else {
checkForVideos();
}
// 定期检查是否有新的视频元素出现
setInterval(checkForVideos, 2000);
// 增强的定期检查逻辑
setInterval(() => {
const videos = document.getElementsByTagName('video');
Array.from(videos).forEach(video => {
if (video.playbackRate !== currentSpeed) {
try {
// 应用速度设置
video.defaultPlaybackRate = currentSpeed;
originalSetPlaybackRate.call(video, currentSpeed);
if (video.webkitPlaybackRate !== undefined) {
video.webkitPlaybackRate = currentSpeed;
}
// 监听 loadeddata 事件,确保在视频加载后应用速度
video.addEventListener('loadeddata', () => {
setTimeout(() => {
originalSetPlaybackRate.call(video, currentSpeed);
}, 100);
}, { once: true });
// 监听 play 事件,确保在播放时应用速度
video.addEventListener('play', () => {
setTimeout(() => {
originalSetPlaybackRate.call(video, currentSpeed);
}, 100);
}, { once: true });
} catch (e) {
console.error('Error updating video speed:', e);
}
}
});
}, 1000); // 增加检查间隔到1秒
// 添加 DOM 变化监听,处理动态加载的视频
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes.length) {
setTimeout(() => {
checkForVideos();
applySpeedToAllVideos();
}, 500);
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// Start the script
initialize();
})();