视频倍速工具

除youtube以外按c加速,按x减速,按z复位,youtube按v加速。点击右上角油猴按钮可开关视频速度记忆功能。

当前为 2023-09-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         视频倍速工具
// @namespace    http://tampermonkey.net/
// @version      0.2.5
// @description  除youtube以外按c加速,按x减速,按z复位,youtube按v加速。点击右上角油猴按钮可开关视频速度记忆功能。
// @author       call duck
// @match        *://*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @run-at document-end
// @license MIT
// ==/UserScript==

// 使用方法:除youtube以外按c加速,按x减速,按z复位,youtube按v加速。

(function () {
  "use strict";
  if (window.innerWidth < 100) {
    return;
  }

  const REMEMBER_KEY = `${location.hostname}_da4e1a5f0c5b46cdadadf70af3c19f37`;
  const SPEED_KEY = "2779054e81654c2990dd9d2bfe9202aa";
  const originSpeed = localStorage.getItem(SPEED_KEY);
  let isRememberSpeed = GM_getValue(REMEMBER_KEY, false);
  let menuId;

  function addRememberMenu() {
    if (!isRememberSpeed) {
      addOpenRememberMenu();
    } else {
      addCloseRememberMenu();
    }
  
    GM_addValueChangeListener(
      REMEMBER_KEY,
      function name(key, oldValue, newValue) {
        GM_unregisterMenuCommand(menuId);
        if (newValue === true) {
          addCloseRememberMenu();
        } else {
          addOpenRememberMenu();
        }
      }
    );
  }


  async function addOpenRememberMenu() {
    if (menuId) GM_unregisterMenuCommand(menuId);
    menuId = await GM.registerMenuCommand("打开视频速度记忆功能", async () => {
      GM_setValue(REMEMBER_KEY, true);
      notify("视频播放速度记忆功能已打开");
    });
  }

  async function addCloseRememberMenu() {
    if (menuId) GM_unregisterMenuCommand(menuId);
    menuId = await GM.registerMenuCommand("关闭视频速度记忆功能", async () => {
      GM_setValue(REMEMBER_KEY, false);
      notify("视频播放速度记忆功能已关闭");
    });
  }

  let video;
  let speed = originSpeed || 1;

  video = document.getElementsByTagName("video")[0];



  new Promise((res) => {
    if (!video) {
      let count = 0;
      const maxCount = 3;
      function getVideo() {
        setTimeout(() => {
          video = document.getElementsByTagName("video")[0];
          if (window.location.hostname==='www.reddit.com') { // reddit特殊处理

            const player =document.querySelector('shreddit-player')
            if (player) {
              video = player.shadowRoot.querySelector('video');
            }

          }
          count++;
          if (!video && count < maxCount) {
            getVideo();
          } else {
            res(video);
          }
        }, 1000);
      }
      getVideo();
    } else {
      res(video);
    }
  }).then((video) => {
    handleVideo(video);
  });

  /*     document.addEventListener('click', (e) => {
            console.log(e.target)
            videos = Array.from(document.getElementsByTagName('video'))
            for (let i = 0; i < videos.length; i++) {
                const v = videos[i];
                handleVideo(v)
            }
        })
 
        function findVideoInDom(dom) {
            var v = dom.getElementsByTagName('video')[0]
            if (!v && !dom.shadowRoot) {
                return null
            }
            if (!v && dom.shadowRoot) {
                findVideoInDom(dom.shadowRoot)
            }
        }
    */
  function changeSpeed(video, speed) {
    video.playbackRate = speed;
    localStorage.setItem(SPEED_KEY, speed);
    notify(video.playbackRate);
  }

  function handleVideo(video) {
    if (video && video.tagName && video.tagName.toLowerCase() === "video") {
      const step = 0.05;
      if (isRememberSpeed) {
        video.playbackRate = speed;
      } else {
        speed = video.playbackRate;
      }
      addRememberMenu()

      video.addEventListener("ratechange", function () {
        speed = video.playbackRate;
      });
      window.addEventListener("keypress", function (e) {
        const activeElement = this.document.activeElement;

        if (
          activeElement.tagName.toLowerCase() === "input" ||
          activeElement.contentEditable === true ||
          activeElement.tagName.toLowerCase() === "textarea"
        ) {
          return;
        }
        if (e.key.toLowerCase() === "c") {
          if (this.location.hostname === "www.youtube.com") {
            return;
          }
          speed = Math.round((speed + step) * 100) / 100;
          changeSpeed(video, speed);
        }
        // youtube单独处理。
        if (
          this.location.hostname === "www.youtube.com" &&
          e.key.toLowerCase() === "v"
        ) {
          speed = Math.round((speed + step) * 100) / 100;
          changeSpeed(video, speed);
        }
        if (e.key.toLowerCase() === "x") {
          speed = Math.round((speed - step) * 100) / 100;
          changeSpeed(video, speed);
        }
        if (e.key.toLowerCase() === "z") {
          speed = 1;
          changeSpeed(video, speed);
        }
      });
      video.addEventListener("playing", () => {
        setTimeout(() => {
          video.playbackRate = speed;
        }, 1);
      });
      video.addEventListener("play", () => {
        setTimeout(() => {
          video.playbackRate = speed;
        }, 1);
      });
    }
  }

  function notify(msg) {
    const className = "edbe85b469d47a8833b84e259864e33";
    const box = document.createElement("div");
    box.className = className;
    box.style.background = "#333";
    box.style.color = "#fff";
    box.style.padding = "8px 20px";
    box.style.position = "fixed";
    box.style.margin = "auto";
    box.style.left = "50%";
    box.style.top = "60px";
    box.style.transform = "translateX(-50%)";
    box.style.borderRadius = "5px";
    box.style.zIndex = "10000";
    box.style.fontSize = "16px";
    box.innerHTML = msg;
    const oldBox = document.querySelectorAll("." + className);
    if (oldBox.length) {
      oldBox.forEach((b) => {
        b.remove();
      });
    }
    if (video && document.fullscreenElement) {
      video.parentElement.appendChild(box);
    } else {
      document.body.appendChild(box);
    }
    setTimeout(() => {
      box.remove();
    }, 2000);
  }
})();