B站/Youtube 倍速快捷键(Z/X/C)+ 宽屏提示(中下方居中)

分段倍速调节,提示显示在播放器底部中间,支持B站宽屏切换,YouTube字幕键屏蔽等功能。

目前為 2025-05-30 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         B站/Youtube 倍速快捷键(Z/X/C)+ 宽屏提示(中下方居中)
// @version      1.1
// @description  分段倍速调节,提示显示在播放器底部中间,支持B站宽屏切换,YouTube字幕键屏蔽等功能。
// @author       重音(support by GPT)
// @match        https://www.bilibili.com/*
// @match        https://www.youtube.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      GPL
// @namespace https://greasyfork.org/users/1476609
// ==/UserScript==

(function () {
  "use strict";

  let isActive = 1;
  let video = null;
  let currentRate = GM_getValue("a", 10);
  let lastRate = GM_getValue("b", 10);

  function updateVideoElement() {
    video = document.querySelector("video") || document.querySelector("bwp-video");
  }

  function setupFocusHandlers() {
    const inputs = [
      document.querySelector(".reply-box-textarea"),
      document.querySelector(".bpx-player-dm-input"),
      document.querySelector(".nav-search-input")
    ];
    inputs.forEach(input => {
      if (input) {
        input.addEventListener("focus", () => isActive = 0);
        input.addEventListener("blur", () => isActive = 1);
      }
    });
  }

  window.addEventListener("load", () => {
    updateVideoElement();
    setupFocusHandlers();
  });

  new MutationObserver(() => {
    updateVideoElement();
    setupFocusHandlers();
  }).observe(document.body, { childList: true, subtree: true });

  setInterval(() => {
    if (video) {
      video.playbackRate = currentRate / 10;
    }
  }, 600);

  // ✅ 创建气泡提示容器(底部中间靠上)
  const tip = document.createElement("div");
  tip.style.cssText = `
    position: absolute;
    left: 50%;
    bottom: 45px;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.75);
    color: #fff;
    padding: 6px 12px;
    border-radius: 8px;
    font-size: 14px;
    z-index: 99999;
    opacity: 0;
    transition: opacity 0.3s;
    pointer-events: none;
  `;

  function attachTipToPlayer() {
    const player = document.querySelector(".bpx-player-container") || document.querySelector(".html5-video-player") || document.querySelector("video")?.parentElement;
    if (player && player.appendChild && !tip.parentElement) {
      if (getComputedStyle(player).position === "static") {
        player.style.position = "relative";
      }
      player.appendChild(tip);
    }
  }
  attachTipToPlayer();
  setInterval(attachTipToPlayer, 1000);

  let tipTimer = null;
  function showTip(text) {
    tip.textContent = text;
    tip.style.opacity = "1";
    clearTimeout(tipTimer);
    tipTimer = setTimeout(() => {
      tip.style.opacity = "0";
    }, 1200);
  }

  function getStep(rate10) {
    const real = rate10 / 10;
    if (real < 2) return 2;
    if (real < 4) return 5;
    return 10;
  }

  document.addEventListener("keydown", function (e) {
    if (!isActive) return;
    const key = e.code;
    const isBili = location.hostname.includes("bilibili.com");
    const isYouTube = location.hostname.includes("youtube.com");

    // 屏蔽 YouTube 原生 C 字幕快捷键
    if (isYouTube && key === "KeyC") {
      e.stopImmediatePropagation();
      e.preventDefault();
    }

    if (!video) return;

    currentRate = Math.round(10 * video.playbackRate);
    const step = getStep(currentRate);
    let changed = false;

    if (key === "KeyX") {
      e.preventDefault();
      currentRate -= step;
      changed = true;
    } else if (key === "KeyC") {
      e.preventDefault();
      currentRate += step;
      changed = true;
    } else if (key === "KeyZ") {
      e.preventDefault();
      currentRate = video.playbackRate === 1.0 ? lastRate : 10;
      changed = true;
    } else if (key === "KeyT") {
      if (isBili) {
        e.preventDefault(); // 仅 B 站拦截
        const wideBtn = document.querySelector(".bpx-player-ctrl-wide");
        if (wideBtn) {
          wideBtn.click();
        }
      }
    }

    currentRate = Math.max(2, Math.min(currentRate, 80));

    if (changed) {
      GM_setValue("a", currentRate);
      if (key !== "KeyZ") {
        lastRate = currentRate;
        GM_setValue("b", lastRate);
      }
      video.playbackRate = currentRate / 10;
      showTip(`播放速度:${(currentRate / 10).toFixed(1)}x`);
    }
  }, true);

  setInterval(() => {
    const rateDisplay = document.querySelector(".bpx-player-ctrl-playbackrate-result");
    if (rateDisplay) {
      const val = parseFloat(rateDisplay.textContent.replace("x", ""));
      if (!isNaN(val)) {
        currentRate = Math.round(val * 10);
        GM_setValue("a", currentRate);
      }
    }
  }, 2000);
})();