Greasy Fork is available in English.

bilibili 倍速控制

使用快捷键控制bilibili的倍速选择

// ==UserScript==
// @name         bilibili 倍速控制
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  使用快捷键控制bilibili的倍速选择
// @author       pipizhu
// @match        http*://www.bilibili.com/video/*
// @match        *://www.youtube.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license 		 MIT
// ==/UserScript==
/* jshint esversion: 8 */

(function() {
  "use strict";
  document.body.onload = init;
})();

const speedList = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3];

const upKeyCode = "Period";
const downKeyCode = "Comma";
let customUpKey = GM_getValue("customUpKey");
let customDownKey = GM_getValue("customDownKey");

async function init() {
  // addActionBtns();
  console.log("init keybindings");
  GM_registerMenuCommand("打开设置面板", openSettings);
  try {
    bindKeys();
    const cachedSpeed = GM_getValue("currentSpeed");
    // await delay(1000);
    if (cachedSpeed) {
      console.log("cached speed", cachedSpeed);
      GM_registerMenuCommand("✅当前记忆倍速 X" + cachedSpeed, () => { });
      setSpeed(cachedSpeed);
    }
    console.log("bind success!");
  } catch (e) {
    console.log("error", e);
  }
}

function getCurrentSpeed() {
  const video = getCurrentVideo();
  return video.playbackRate;
}

function bindKeys() {
  const keys = "0123456789";
  document.addEventListener(
    "keydown",
    (e) => {
      if (
        keys.indexOf(e.key) != -1 &&
        !(e.isComposing || e.ctrlKey || e.altKey)
      ) {
        e.stopImmediatePropagation();
      }
      if (e.key === "2") {
        setSpeed(2);
        return;
      }
      if (e.key === "5") {
        setSpeed(1.5);
        return;
      }
      if (e.key === "7") {
        setSpeed(1.75);
        return;
      }
      if (e.key === "1" || e.key === "z") {
        setSpeed(1);
        return;
      }
      if (e.key === "3") {
        setSpeed(3);
        return;
      }
      const video = getCurrentVideo();
      const currentSpeed = video.playbackRate;
      const isCustom = customUpKey && customDownKey;
      let currentIndex = speedList.findIndex((s) => s === currentSpeed);
      if (isCustom) {
        if (e.key === customUpKey) {
          currentIndex = Math.min(currentIndex + 1, speedList.length - 1);
        }
        if (e.key === customDownKey) {
          currentIndex = Math.max(currentIndex - 1, 0);
        }
      } else {
        if (e.code === upKeyCode && e.shiftKey) {
          // increase speed
          currentIndex = Math.min(currentIndex + 1, speedList.length - 1);
        } else if (e.code === downKeyCode && e.shiftKey) {
          // decrease speed
          currentIndex = Math.max(currentIndex - 1, 0);
        }
      }
      const speed = speedList[currentIndex];
      setSpeed(speed);
    },
    true,
  );
}

function setSpeed(speed) {
  const video = getCurrentVideo();
  video.playbackRate = speed;
}

async function delay(time) {
  return new Promise((res) => setTimeout(() => res(), time));
}

function getCurrentVideo() {
  const video = document.getElementsByTagName("video")[0];
  return video;
}

function addStyle(styleString) {
  const style = document.createElement("style");
  style.type = "text/css";
  style.appendChild(document.createTextNode(styleString));
  document.head.append(style);
}

function preserveSpeed() {
  const currentSpeed = getCurrentSpeed();
  GM_setValue("currentSpeed", currentSpeed);
}

function clearSpeed() {
  GM_setValue("currentSpeed", "");
}

function openSettings() {
  const speed = getCurrentSpeed();
  const currentKey = {
    up: customUpKey || ">",
    down: customDownKey || "<",
  };
  const html = `
    <div class='bli-speed-pannel-setting' id='bli-speed-pannel-setting' style='position: fixed; bottom: 0; right: 0;top: 0;left:0; z-index: 9999;display:flex;justify-content: center;align-items: center'>
      <div style='position:absolute; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5);z-index: -1'></div>
      <div style='background: white;padding:20px;border-radius: 5px;'>
        <h2>当前速度:${speed}</h2>
        <div>
          <button id='save-speed'> 记忆当前速度 </button>
          <button id='clear-speed'> 取消记忆当前速度 </button>
        </div>

        <h2>当前快捷键</h2>
        <div>加速键:${currentKey.up} 减速键:${currentKey.down}</div>

        <input type='checkbox' id='custom-speed-checkbox' />
        <label for="custom-speed-checkbox">自定义快捷键</label>
        <div>
          <button id='custom-down-key'>设置减速快捷键 </button>
          <button id='custom-up-key'>设置加速快捷键 </button>
        </div>

        <button style='display: block; margin: 0 auto;margin-top: 20px; ' id='close-pannel-setting'>关闭面板</button>
      </div>
    </div>
  `;
  document.body.insertAdjacentHTML("beforeend", html);
  addStyle(`
    .bli-speed-pannel-setting button{
      background: transparent;
      border: 1px solid;
      padding: 5px 8px;
      border-radius: 25px;
      cursor: pointer;
    }
    .bli-speed-pannel-setting button:disabled{
      cursor: not-allowed;
      opacity: 0.5;
    }
    .bli-speed-pannel-setting h2{
      text-align: center;
    }
  `);

  const closePannel = () => {
    document.body.removeChild(
      document.getElementById("bli-speed-pannel-setting"),
    );
  };

  const upKey = GM_getValue("customUpKey");
  const downKey = GM_getValue("customDownKey");
  const isCustom = upKey && downKey;
  const customCheckBox = document.getElementById("custom-speed-checkbox");
  const upBtn = document.getElementById("custom-up-key");
  const downBtn = document.getElementById("custom-down-key");
  customCheckBox.checked = isCustom;
  upBtn.disabled = !isCustom;
  downBtn.disabled = !isCustom;
  if (upKey) {
    upBtn.innerText = "加速键:" + upKey;
  }
  if (downKey) {
    downBtn.innerText = "减速键:" + downKey;
  }

  function handleUpKey(e) {
    const key = e.key;
    customUpKey = key;
    GM_setValue("customUpKey", key);
    upBtn.innerText = "加速键:" + key;
    document.removeEventListener("keydown", handleUpKey);
  }
  function handleDownKey(e) {
    const key = e.key;
    customDownKey = key;
    GM_setValue("customDownKey", key);
    downBtn.innerText = "减速键:" + key;
    document.removeEventListener("keydown", handleDownKey);
  }
  upBtn.addEventListener("click", () => {
    upBtn.innerText = "请输入加速键";
    document.removeEventListener("keydown", handleDownKey);
    document.addEventListener("keydown", handleUpKey);
  });

  downBtn.addEventListener("click", () => {
    downBtn.innerText = "请输入减速键";
    document.removeEventListener("keydown", handleUpKey);
    document.addEventListener("keydown", handleDownKey);
  });

  customCheckBox.addEventListener("change", (e) => {
    const checked = e.target.checked;
    upBtn.disabled = !checked;
    downBtn.disabled = !checked;
    if (!checked) {
      GM_setValue("customUpKey", "");
      GM_setValue("customDownKey", "");
    }
  });

  document.getElementById("save-speed").addEventListener("click", () => {
    preserveSpeed();
    closePannel();
  });
  document.getElementById("clear-speed").addEventListener("click", () => {
    clearSpeed();
    closePannel();
  });
  document
    .getElementById("close-pannel-setting")
    .addEventListener("click", () => {
      closePannel();
    });
}