Greasy Fork

视频精确控制工具

为不方便进行控制的网站添加精确控制的工具条,悬浮在屏幕下方

目前为 2025-02-27 提交的版本。查看 最新版本

// ==UserScript==
// @name         视频精确控制工具
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  为不方便进行控制的网站添加精确控制的工具条,悬浮在屏幕下方
// @author       wen2so
// @match        *://*/*
// @grant        none
// @license      MIT
// ==/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 = `
    <option value="0.5">0.5x</option>
    <option value="1" selected>1x</option>
    <option value="1.5">1.5x</option>
    <option value="2">2x</option>
  `;
    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,
    });


})();