VideoControlReset

视频增强插件:手动增强、跨域安全、Lucide图标、美化控制条、音量调节、进度条、iframe支持、键盘快捷键等。

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         VideoControlReset
// @namespace    https://greasyfork.org/
// @version      1.0
// @description  视频增强插件:手动增强、跨域安全、Lucide图标、美化控制条、音量调节、进度条、iframe支持、键盘快捷键等。
// @author       DM
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  console.log("[🎬 Video Enhancer] 脚本加载成功 ✅");

  /*** 引入 Lucide 图标库 ***/
  const ICON_URL = "https://unpkg.com/lucide@latest/dist/umd/lucide.js";
  if (!window.lucide) {
    const script = document.createElement("script");
    script.src = ICON_URL;
    script.onload = () => window.lucide.createIcons();
    document.head.appendChild(script);
  }

  /** 样式 **/
  const style = document.createElement("style");
  style.textContent = `
    .ve-enhance-btn-box {
      position: absolute;
      top: 8px;
      right: 8px;
      display: flex;
      gap: 6px;
      z-index: 999999;
    }
    .ve-btn {
      border: none;
      background: rgba(0,0,0,0.5);
      color: #fff;
      border-radius: 8px;
      cursor: pointer;
      padding: 6px;
      display: flex;
      align-items: center;
      transition: all .2s ease;
    }
    .ve-btn:hover {
      background: rgba(0,0,0,0.8);
      transform: scale(1.15);
    }
    .ve-bar {
      position: absolute;
      left: 50%;
      bottom: 12px;
      transform: translateX(-50%);
      display: flex;
      align-items: center;
      gap: 10px;
      background: rgba(0,0,0,0.45);
      padding: 8px 14px;
      border-radius: 20px;
      backdrop-filter: blur(6px);
      opacity: 0;
      transition: opacity 0.3s ease;
      z-index: 999999;
    }
    .video-wrapper:hover .ve-bar {
      opacity: 1;
    }
    .ve-progress {
      width: 160px;
      height: 5px;
      background: rgba(255,255,255,0.3);
      border-radius: 3px;
      cursor: pointer;
      position: relative;
      overflow: hidden;
    }
    .ve-progress-inner {
      height: 100%;
      width: 0%;
      background: linear-gradient(90deg,#4fc3f7,#81c784,#f06292);
      border-radius: 3px;
      transition: width 0.1s linear;
    }
    .ve-volume { width: 80px; }
  `;
  document.head.appendChild(style);

  /** 工具函数 **/
  const createIcon = (name, size = 20) => {
    const i = document.createElement("i");
    i.dataset.lucide = name;
    i.style.width = `${size}px`;
    i.style.height = `${size}px`;
    return i;
  };

  const createButton = (icon, title, onClick) => {
    const btn = document.createElement("button");
    btn.className = "ve-btn";
    btn.title = title;
    btn.appendChild(createIcon(icon));
    btn.onclick = onClick;
    return btn;
  };

  /** 截图功能 **/
  function captureFrame(video) {
    const canvas = document.createElement("canvas");
    canvas.width = video.videoWidth;
    canvas.height = video.videoHeight;
    canvas.getContext("2d").drawImage(video, 0, 0);
    const link = document.createElement("a");
    link.href = canvas.toDataURL("image/png");
    link.download = "screenshot.png";
    link.click();
  }

  /** 增强核心函数 **/
  function enhanceVideo(video) {
    if (video.dataset.enhanced) return;
    video.dataset.enhanced = "true";
    const wrapper = video.parentElement;
    wrapper.classList.add("video-wrapper");

    const bar = document.createElement("div");
    bar.className = "ve-bar";

    const playBtn = createButton("play", "播放/暂停", () => {
      video.paused ? video.play() : video.pause();
    });

    const backBtn = createButton("rewind", "后退5秒", () => video.currentTime -= 5);
    const forwardBtn = createButton("fast-forward", "前进5秒", () => video.currentTime += 5);

    const speedBtn = createButton("gauge", "倍速切换", () => {
      const speeds = [1, 1.25, 1.5, 2];
      const next = speeds[(speeds.indexOf(video.playbackRate) + 1) % speeds.length];
      video.playbackRate = next;
      speedBtn.title = `倍速:${next}x`;
    });

    const pipBtn = createButton("monitor-up", "画中画", () => video.requestPictureInPicture());
    const fsBtn = createButton("maximize", "全屏", () => video.requestFullscreen());
    const shotBtn = createButton("camera", "截图", () => captureFrame(video));

    // 进度条
    const progress = document.createElement("div");
    progress.className = "ve-progress";
    const inner = document.createElement("div");
    inner.className = "ve-progress-inner";
    progress.appendChild(inner);
    progress.onclick = (e) => {
      const rect = progress.getBoundingClientRect();
      const percent = (e.clientX - rect.left) / rect.width;
      video.currentTime = percent * video.duration;
    };
    video.addEventListener("timeupdate", () => {
      inner.style.width = `${(video.currentTime / video.duration) * 100}%`;
    });

    // 音量
    const volBox = document.createElement("div");
    volBox.style.display = "flex";
    volBox.style.alignItems = "center";
    const volIcon = createIcon("volume-2");
    const volSlider = document.createElement("input");
    volSlider.type = "range";
    volSlider.className = "ve-volume";
    volSlider.min = 0;
    volSlider.max = 1;
    volSlider.step = 0.05;
    volSlider.value = video.volume;
    volSlider.oninput = () => {
      video.volume = volSlider.value;
      volIcon.dataset.lucide = video.volume == 0 ? "volume-x" : "volume-2";
      window.lucide && window.lucide.createIcons();
    };
    volBox.append(volIcon, volSlider);

    [playBtn, backBtn, forwardBtn, speedBtn, pipBtn, fsBtn, shotBtn, progress, volBox].forEach(b => bar.appendChild(b));
    wrapper.appendChild(bar);

    // 播放状态联动
    const updatePlayIcon = () => {
      playBtn.firstChild.dataset.lucide = video.paused ? "play" : "pause";
      window.lucide && window.lucide.createIcons();
    };
    video.addEventListener("play", updatePlayIcon);
    video.addEventListener("pause", updatePlayIcon);

    window.lucide && window.lucide.createIcons();
  }

  /** 添加增强/下载按钮 **/
  function addEnhanceButton(video) {
    if (video.dataset.hasBtn) return;
    video.dataset.hasBtn = "true";

    const box = document.createElement("div");
    box.className = "ve-enhance-btn-box";

    const enhance = createButton("sparkles", "增强视频", () => enhanceVideo(video));
    const download = createButton("download", "下载视频", () => {
      const a = document.createElement("a");
      a.href = video.src;
      a.download = "video.mp4";
      a.click();
    });

    box.append(enhance, download);
    video.parentElement.style.position = "relative";
    video.parentElement.appendChild(box);
    window.lucide && window.lucide.createIcons();
  }

  /** 扫描所有视频(支持 iframe) **/
  function scanVideos(root = document) {
    root.querySelectorAll("video").forEach(v => addEnhanceButton(v));

    root.querySelectorAll("iframe").forEach(frame => {
      try {
        const doc = frame.contentDocument || frame.contentWindow.document;
        if (!doc) return;
        const vids = doc.querySelectorAll("video");
        vids.forEach(v => addEnhanceButton(v));
      } catch {
        // 跨域 iframe 忽略
      }
    });
  }

  scanVideos();

  /** 监听增量变化 **/
  const obs = new MutationObserver(mutations => {
    for (const m of mutations)
      m.addedNodes.forEach(n => n.nodeType === 1 && scanVideos(n));
  });
  obs.observe(document.body, { childList: true, subtree: true });

})();