// ==UserScript==
// @name 视频精确控制工具
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 为不方便进行控制的视频添加悬浮在屏幕下方的精确控制工具条
// @author wen2so
// @match *://*/*
// @grant none
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/528200/%E8%A7%86%E9%A2%91%E7%B2%BE%E7%A1%AE%E6%8E%A7%E5%88%B6%E5%B7%A5%E5%85%B7.user.js
// @updateURL https://update.greasyfork.icu/scripts/528200/%E8%A7%86%E9%A2%91%E7%B2%BE%E7%A1%AE%E6%8E%A7%E5%88%B6%E5%B7%A5%E5%85%B7.meta.js
// ==/UserScript==
(function () {
"use strict";
// 创建悬浮控制面板
const controller = document.createElement("div");
controller.style.cssText = `
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.2);
padding: 10px;
border-radius: 25px;
display: flex;
gap: 10px;
z-index: 99999;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
backdrop-filter: blur(5px);
align-items: center;
`;
// 创建控制按钮
const createButton = (text, seconds) => {
const btn = document.createElement("button");
btn.textContent = text;
btn.style.cssText = `
padding: 8px 16px;
border: none;
border-radius: 15px;
background: rgba(100,255,255,0.2);
color: white;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
`;
btn.addEventListener("click", () => adjustVideoTime(seconds));
btn.addEventListener(
"mousedown",
() => (btn.style.transform = "scale(0.95)")
);
btn.addEventListener("mouseup", () => (btn.style.transform = "scale(1)"));
btn.addEventListener(
"mouseleave",
() => (btn.style.transform = "scale(1)")
);
return btn;
};
// 创建按钮组容器
const buttonGroupColumn = document.createElement("div");
buttonGroupColumn.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
`;
// 创建后退按钮行
const backButtonRow = document.createElement("div");
backButtonRow.style.cssText = `
display: flex;
gap: 10px;
justify-content: flex-end;
`;
// 创建前进按钮行(放在上方)
const forwardButtonRow = document.createElement("div");
forwardButtonRow.style.cssText = `
display: flex;
gap: 10px;
justify-content: flex-end;
`;
// 添加按钮到对应容器(注意顺序反转)
forwardButtonRow.appendChild(createButton("5s»", 5));
forwardButtonRow.appendChild(createButton("30s»", 30));
forwardButtonRow.appendChild(createButton("1m»", 60));
backButtonRow.appendChild(createButton("«1m", -60));
backButtonRow.appendChild(createButton("«30s", -30));
backButtonRow.appendChild(createButton("«5s", -5));
// 将按钮行添加到列容器(前进在上方)
buttonGroupColumn.appendChild(forwardButtonRow);
buttonGroupColumn.appendChild(backButtonRow);
// 将按钮组添加到控制面板
controller.appendChild(buttonGroupColumn);
// 创建工具组容器
const toolGroupColumn = document.createElement("div");
buttonGroupColumn.style.cssText = `
display: flex;
flex-direction: column;
gap: 8px;
`;
// 创建倍率行
const speedRow = document.createElement("div");
speedRow.style.cssText = `
display: flex;
gap: 10px;
justify-content: flex-end;
`;
// 添加播放速率控制
const speedControl = document.createElement("select");
speedControl.innerHTML = `
`;
speedControl.style.cssText = `
background: rgba(0,0,0,0.2);
color: white;
border: none;
border-radius: 15px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
`;
speedControl.onchange = (e) => {
document
.querySelectorAll("video")
.forEach((v) => (v.playbackRate = e.target.value));
};
// 创建精确控制行
const progressRow = document.createElement("div");
progressRow.style.cssText = `
display: flex;
gap: 10px;
justify-content: flex-end;
`;
// 添加精确进度控制
const progressContainer = document.createElement("div");
progressContainer.style.cssText = `
display: flex;
align-items: center;
gap: 10px;
`;
// 当前时间显示
const currentTimeDisplay = document.createElement("span");
currentTimeDisplay.textContent = "00:00";
currentTimeDisplay.style.cssText = `
color: white;
font-size: 14px;
`;
// 精确时间输入框
const timeInput = document.createElement("input");
timeInput.type = "number";
timeInput.min = 0;
timeInput.step = 1;
timeInput.style.cssText = `
width: 80px;
padding: 8px;
border: none;
border-radius: 15px;
background: rgba(0,0,0,0.2);
color: white;
font-size: 14px;
`;
// 创建应用按钮
const applyButton = document.createElement("button");
applyButton.textContent = "应用";
applyButton.style.cssText = `
padding: 8px 16px;
border: none;
border-radius: 15px;
background: rgba(100,255,255,0.2);
color: white;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
`;
applyButton.addEventListener("click", () => {
const time = parseFloat(timeInput.value);
if (!isNaN(time)) {
document.querySelectorAll("video").forEach((v) => {
const validTime = Math.max(0, Math.min(time, v.duration));
v.currentTime = validTime;
timeInput.value = validTime; // 更新输入框为实际应用的时间
});
}
});
applyButton.addEventListener(
"mousedown",
() => (applyButton.style.transform = "scale(0.95)")
);
applyButton.addEventListener(
"mouseup",
() => (applyButton.style.transform = "scale(1)")
);
applyButton.addEventListener(
"mouseleave",
() => (applyButton.style.transform = "scale(1)")
);
speedRow.appendChild(speedControl);
speedRow.appendChild(applyButton);
toolGroupColumn.appendChild(speedRow);
controller.appendChild(toolGroupColumn);
progressRow.appendChild(currentTimeDisplay);
progressRow.appendChild(timeInput);
toolGroupColumn.appendChild(progressRow);
controller.appendChild(toolGroupColumn);
// 插入控制面板
document.body.appendChild(controller);
// 调整视频时间
function adjustVideoTime(seconds) {
const videos = document.querySelectorAll("video");
videos.forEach((video) => {
try {
const newTime = video.currentTime + seconds;
video.currentTime = Math.max(0, Math.min(newTime, video.duration));
} catch (error) {
console.log("视频控制错误:", error);
}
});
}
// 更新当前时间和输入框
function updateTimeDisplay() {
const video = document.querySelector("video");
if (video) {
const currentTime = Math.floor(video.currentTime);
const duration = Math.floor(video.duration);
currentTimeDisplay.textContent = `${formatTime(
currentTime
)} / ${formatTime(duration)}`;
// 仅在输入框未激活时更新其值
if (document.activeElement !== timeInput) {
timeInput.value = currentTime;
}
}
}
// 格式化时间(秒 -> 分钟:秒)
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? "0" : ""}${secs}`;
}
// 监听视频时间更新
setInterval(updateTimeDisplay, 500);
// 动态内容检测(针对SPA)
const observer = new MutationObserver(() => {
if (!document.body.contains(controller)) {
document.body.appendChild(controller);
}
});
observer.observe(document, {
childList: true,
subtree: true,
});
})();