视频精确控制工具(优化版)

为手机端视频添加可开关的悬浮精确控制工具条,完全透明背景不影响观看内容

// ==UserScript==
// @name         视频精确控制工具(优化版)
// @namespace    http://tampermonkey.net/
// @version      2.3
// @description  为手机端视频添加可开关的悬浮精确控制工具条,完全透明背景不影响观看内容
// @author       wen2so
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // 配置 - 调整为完全透明背景
  const CONFIG = {
    defaultEnabled: true,
    panelWidth: '94%',
    borderRadius: '18px',
    backgroundColor: 'rgba(30, 30, 30, 0.4)', // 更透明的背景
    buttonColor: 'rgba(50, 50, 50, 0.6)',
    accentColor: 'rgba(80, 110, 190, 0.5)',
    textColor: 'rgba(240, 240, 240, 0.95)',
    fontSize: '14px',
    buttonPadding: '8px 12px',
    gap: '8px'
  };

  // 状态管理
  let isEnabled = GM_getValue('videoControllerEnabled', CONFIG.defaultEnabled);
  let controller = null;
  let toggleButton = null;
  let timeUpdateInterval = null;

  // 创建主控制面板
  function createController() {
    if (controller) return;

    controller = document.createElement("div");
    controller.id = "video-precise-controller";
    controller.style.cssText = `
      position: fixed;
      bottom: 85px;
      left: 50%;
      transform: translateX(-50%);
      background: ${CONFIG.backgroundColor};
      padding: 16px;
      border-radius: ${CONFIG.borderRadius};
      display: flex;
      flex-direction: column;
      gap: ${CONFIG.gap};
      z-index: 2147483647;
      /* 移除磨砂效果,让背景完全清晰可见 */
      box-shadow: 0 2px 8px rgba(0,0,0,0.15);
      width: ${CONFIG.panelWidth};
      max-width: 420px;
      font-size: ${CONFIG.fontSize};
      color: ${CONFIG.textColor};
      transition: all 0.3s ease;
      border: 1px solid rgba(255,255,255,0.08);
      ${isEnabled ? 'opacity: 1; transform: translateX(-50%) translateY(0);' : 'opacity: 0; transform: translateX(-50%) translateY(20px); pointer-events: none;'}
    `;

    // 创建主控制区域 - 充分利用空间的网格布局
    const mainGrid = document.createElement("div");
    mainGrid.style.cssText = `
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: ${CONFIG.gap};
      margin-bottom: 12px;
    `;

    // 第一行:大按钮
    const largeButtonRow = document.createElement("div");
    largeButtonRow.style.cssText = `
      display: flex;
      gap: ${CONFIG.gap};
      grid-column: 1 / -1;
    `;

    // 添加5s/30s/1min按钮(按要求修改)
    const jumpButtons = [
      { text: "« 1min", seconds: -60 },
      { text: "« 30s", seconds: -30 },
      { text: "« 5s", seconds: -5 },
      { text: "5s »", seconds: 5 },
      { text: "30s »", seconds: 30 },
      { text: "1min »", seconds: 60 }
    ];

    jumpButtons.forEach(btnConfig => {
      const button = createLargeButton(btnConfig.text, () => adjustVideoTime(btnConfig.seconds));
      largeButtonRow.appendChild(button);
    });

    mainGrid.appendChild(largeButtonRow);
    controller.appendChild(mainGrid);

    // 创建底部工具栏 - 水平排列充分利用空间
    const toolsContainer = document.createElement("div");
    toolsContainer.style.cssText = `
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      padding: 8px 0 0 0;
    `;

    // 左侧:播放速度控制
    const speedContainer = document.createElement("div");
    speedContainer.style.cssText = `
      display: flex;
      align-items: center;
      gap: 8px;
    `;

    const speedLabel = document.createElement("span");
    speedLabel.textContent = "速度:";
    speedLabel.style.cssText = `font-size: 13px;`;

    const speedControl = document.createElement("select");
    speedControl.innerHTML = `
      <option value="0.25">0.25x</option>
      <option value="0.5">0.5x</option>
      <option value="0.75">0.75x</option>
      <option value="1" selected>1x</option>
      <option value="1.25">1.25x</option>
      <option value="1.5">1.5x</option>
      <option value="2">2x</option>
      <option value="4">4x</option>
    `;
    speedControl.style.cssText = `
      background: ${CONFIG.buttonColor};
      color: ${CONFIG.textColor};
      border: none;
      border-radius: 8px;
      padding: 6px 10px;
      font-size: 13px;
      cursor: pointer;
      appearance: none;
      -webkit-appearance: none;
      min-width: 65px;
    `;
    speedControl.onchange = (e) => {
      document.querySelectorAll("video").forEach((v) => {
        v.playbackRate = parseFloat(e.target.value);
      });
    };

    speedContainer.appendChild(speedLabel);
    speedContainer.appendChild(speedControl);

    // 右侧:精确时间控制
    const timeContainer = document.createElement("div");
    timeContainer.style.cssText = `
      display: flex;
      align-items: center;
      gap: 8px;
    `;

    const currentTimeDisplay = document.createElement("span");
    currentTimeDisplay.textContent = "00:00:00 / 00:00:00";
    currentTimeDisplay.style.cssText = `
      font-size: 13px;
      font-family: monospace;
      min-width: 120px;
      text-align: right;
    `;

    const timeInput = document.createElement("input");
    timeInput.type = "number";
    timeInput.min = 0;
    timeInput.step = 1;
    timeInput.placeholder = "秒";
    timeInput.style.cssText = `
      width: 65px;
      padding: 6px 8px;
      border: none;
      border-radius: 8px;
      background: ${CONFIG.buttonColor};
      color: ${CONFIG.textColor};
      font-size: 13px;
      text-align: center;
    `;

    const applyButton = createIconButton("✓", () => {
      const time = parseFloat(timeInput.value);
      if (!isNaN(time)) {
        document.querySelectorAll("video").forEach((v) => {
          const validTime = Math.max(0, Math.min(time, v.duration || Infinity));
          v.currentTime = validTime;
        });
      }
    });
    applyButton.style.cssText += `padding: 6px 10px;`;

    timeContainer.appendChild(currentTimeDisplay);
    timeContainer.appendChild(timeInput);
    timeContainer.appendChild(applyButton);

    toolsContainer.appendChild(speedContainer);
    toolsContainer.appendChild(timeContainer);
    controller.appendChild(toolsContainer);

    // 插入到页面
    document.body.appendChild(controller);

    // 开始更新时间显示
    if (timeUpdateInterval) clearInterval(timeUpdateInterval);
    timeUpdateInterval = setInterval(updateTimeDisplay.bind(null, currentTimeDisplay, timeInput), 300);

    // 动态内容检测
    const observer = new MutationObserver(() => {
      if (!document.body.contains(controller)) {
        document.body.appendChild(controller);
      }
    });
    observer.observe(document.body, { childList: true, subtree: true });
  }

  // 创建大型按钮(用于5s/30s/1min控制)
  function createLargeButton(text, onClick) {
    const btn = document.createElement("button");
    btn.textContent = text;
    btn.style.cssText = `
      flex: 1;
      padding: 12px 8px;
      border: none;
      border-radius: 10px;
      background: ${CONFIG.accentColor};
      color: ${CONFIG.textColor};
      font-size: 14px;
      font-weight: bold;
      cursor: pointer;
      transition: all 0.2s;
      white-space: nowrap;
      text-align: center;
    `;
    btn.addEventListener("click", onClick);
    btn.addEventListener("touchstart", () => {
      btn.style.transform = "scale(0.97)";
      btn.style.opacity = "0.9";
    });
    btn.addEventListener("touchend", () => {
      btn.style.transform = "scale(1)";
      btn.style.opacity = "1";
    });
    return btn;
  }

  // 创建图标按钮
  function createIconButton(icon, onClick) {
    const btn = document.createElement("button");
    btn.innerHTML = icon;
    btn.style.cssText = `
      padding: 6px 10px;
      border: none;
      border-radius: 8px;
      background: ${CONFIG.buttonColor};
      color: ${CONFIG.textColor};
      font-size: 14px;
      cursor: pointer;
      transition: all 0.2s;
      display: flex;
      align-items: center;
      justify-content: center;
    `;
    btn.addEventListener("click", onClick);
    btn.addEventListener("touchstart", () => {
      btn.style.transform = "scale(0.95)";
      btn.style.opacity = "0.9";
    });
    btn.addEventListener("touchend", () => {
      btn.style.transform = "scale(1)";
      btn.style.opacity = "1";
    });
    return btn;
  }

  // 切换控制器开关
  function toggleController() {
    isEnabled = !isEnabled;
    GM_setValue('videoControllerEnabled', isEnabled);

    if (isEnabled) {
      if (!controller) createController();
      else {
        controller.style.opacity = "1";
        controller.style.transform = "translateX(-50%) translateY(0)";
        controller.style.pointerEvents = "auto";
      }
    } else {
      if (controller) {
        controller.style.opacity = "0";
        controller.style.transform = "translateX(-50%) translateY(20px)";
        controller.style.pointerEvents = "none";
      }
    }
  }

  // 创建悬浮开关按钮
  function createToggleButton() {
    if (toggleButton) return;

    toggleButton = document.createElement("div");
    toggleButton.id = "video-controller-toggle";
    toggleButton.style.cssText = `
      position: fixed;
      bottom: 25px;
      right: 25px;
      width: 45px;
      height: 45px;
      background: ${isEnabled ? CONFIG.accentColor : CONFIG.buttonColor};
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      color: ${CONFIG.textColor};
      font-size: 20px;
      font-weight: bold;
      z-index: 2147483646;
      box-shadow: 0 3px 8px rgba(0,0,0,0.2);
      cursor: pointer;
      transition: all 0.3s ease;
      border: 1px solid rgba(255,255,255,0.1);
    `;
    toggleButton.textContent = isEnabled ? "❚❚" : "▶";

    toggleButton.addEventListener("click", toggleController);
    toggleButton.addEventListener("touchstart", () => {
      toggleButton.style.transform = "scale(0.92)";
      toggleButton.style.opacity = "0.9";
    });
    toggleButton.addEventListener("touchend", () => {
      toggleButton.style.transform = "scale(1)";
      toggleButton.style.opacity = "1";
    });

    document.body.appendChild(toggleButton);
  }

  // 调整视频时间
  function adjustVideoTime(seconds) {
    const videos = document.querySelectorAll("video");
    if (videos.length === 0) return;

    videos.forEach((video) => {
      try {
        const newTime = video.currentTime + seconds;
        video.currentTime = Math.max(0, Math.min(newTime, video.duration || Infinity));
      } catch (error) {
        console.log("视频控制错误:", error);
      }
    });
  }

  // 更新时间显示(支持小时显示)
  function updateTimeDisplay(currentTimeDisplay, timeInput) {
    const video = document.querySelector("video");
    if (!video) return;

    const currentTime = Math.floor(video.currentTime);
    const duration = Math.floor(video.duration || 0);
    currentTimeDisplay.textContent = `${formatTimeHHMMSS(currentTime)} / ${formatTimeHHMMSS(duration)}`;

    // 仅在输入框未聚焦时更新其值
    if (document.activeElement !== timeInput) {
      timeInput.value = currentTime;
    }
  }

  // 格式化时间为 HH:MM:SS 格式
  function formatTimeHHMMSS(seconds) {
    if (isNaN(seconds) || seconds === Infinity) return "--:--:--";
    const hrs = Math.floor(seconds / 3600);
    const mins = Math.floor((seconds % 3600) / 60);
    const secs = Math.floor(seconds % 60);
    if (hrs > 0) {
      return `${hrs}:${mins < 10 ? '0' + mins : mins}:${secs < 10 ? '0' + secs : secs}`;
    } else {
      return `${mins}:${secs < 10 ? '0' + secs : secs}`;
    }
  }

  // 初始化
  function init() {
    // 创建开关按钮
    createToggleButton();

    // 如果启用,则创建控制器
    if (isEnabled) {
      createController();
    }
  }

  // 页面加载完成后初始化
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址