B站调速器

滚轮控制B站视频播放速度,右键恢复1x,刷新/切换视频后保持速度

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         B站调速器
// @namespace    https://github.com/Germxu
// @homepage     https://github.com/Germxu/Scripts-for-TamperMonkey
// @supportURL   https://github.com/Germxu/Scripts-for-TamperMonkey/issues/new
// @version      1.1
// @description  滚轮控制B站视频播放速度,右键恢复1x,刷新/切换视频后保持速度
// @author       Finn (enhanced)
// @match        https://www.bilibili.com/video/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';

  const minRate = 0.3;
  const maxRate = 5.0;
  const STORAGE_KEY_PREFIX = 'bilibili-playback-rate-';

  // 获取当前视频的唯一标识(BV号或路径)
  function getVideoId() {
    const path = location.pathname; // 如 /video/BV1xx411c7mu
    // return path.split('/')[ 2 ] || 'global'; // BV号或 fallback
    return 'global';
  }

  // 获取存储键
  function getStorageKey() {
    return STORAGE_KEY_PREFIX + getVideoId();
  }

  // 保存播放速度
  function savePlaybackRate(rate) {
    try {
      localStorage.setItem(getStorageKey(), rate.toFixed(2));
    } catch (e) {
      console.warn("无法保存播放速度", e);
    }
  }

  // 读取保存的播放速度
  function loadPlaybackRate() {
    try {
      const saved = localStorage.getItem(getStorageKey());
      if (saved) {
        const rate = parseFloat(saved);
        return isNaN(rate) ? null : Math.max(minRate, Math.min(maxRate, rate));
      }
    } catch (e) {
      console.warn("无法读取播放速度", e);
    }
    return null;
  }

  // 查找视频元素
  function getVideoElement() {
    return document.querySelector("video") ||
      document.querySelector("video.bpx-player-video-video-tag");
  }

  // 获取播放倍速按钮
  function getPlaybackRateButton() {
    return document.querySelector("#bilibili-player .bpx-player-ctrl-playbackrate");
  }

  // 设置播放速度并更新 UI + 持久化
  function setPlaybackRate(video, rate) {
    if (!video) return;

    rate = Math.max(minRate, Math.min(maxRate, rate));
    video.playbackRate = rate;

    // 更新按钮显示
    const rateBtn = getPlaybackRateButton();
    if (rateBtn) {
      const label = rateBtn.querySelector("span");
      if (label) {
        label.textContent = rate.toFixed(2) + "x";
      }
    }

    // 持久化存储
    savePlaybackRate(rate);
  }

  // 恢复默认播放速度
  function resetPlaybackRate(video) {
    setPlaybackRate(video, 1.00);
  }

  // 滚轮调节速度
  function handleWheel(event) {
    event.preventDefault();
    const video = getVideoElement();
    if (!video) return;

    let rate = video.playbackRate;
    const step = (rate > 1.2 && rate < 2) ? 0.01 : 0.04;

    if (event.deltaY < 0) {
      rate += step; // 向上滚:加速
    } else {
      rate -= step; // 向下滚:减速
    }

    rate = Math.round(rate * 100) / 100;
    setPlaybackRate(video, rate);
  }

  // 绑定事件到按钮
  function bindEvents(rateBtn) {
    // 防止重复绑定
    rateBtn.removeEventListener("wheel", handleWheel);
    rateBtn.removeEventListener("contextmenu", handleContextMenu);

    rateBtn.addEventListener("wheel", handleWheel, { passive: false });

    rateBtn.addEventListener("contextmenu", handleContextMenu, { passive: false });
  }

  // 右键处理函数
  function handleContextMenu(event) {
    event.preventDefault();
    const video = getVideoElement();
    if (video) {
      resetPlaybackRate(video);
    }
  }

  // 尝试恢复速度并绑定事件
  function tryRestoreAndBind() {
    const rateBtn = getPlaybackRateButton();
    const video = getVideoElement();

    if (rateBtn && video) {
      bindEvents(rateBtn);

      // 恢复保存的速度(如果有)
      const savedRate = loadPlaybackRate();
      if (savedRate !== null && Math.abs(video.playbackRate - savedRate) > 0.01) {
        setPlaybackRate(video, savedRate);
      }

      console.log("B站调速器已激活,速度已恢复:", video.playbackRate.toFixed(2) + "x");
      return true; // 成功绑定
    }
    return false;
  }

  // 使用 MutationObserver 监听播放器动态加载(更可靠)
  function observePlayer() {
    const observer = new MutationObserver((mutations, obs) => {
      if (tryRestoreAndBind()) {
        obs.disconnect(); // 绑定成功后停止观察
      }
    });

    // 开始观察 document.body 的子节点变化
    observer.observe(document.body, {
      childList: true,
      subtree: true
    });

    // 同时尝试立即绑定(防止已加载)
    if (tryRestoreAndBind()) {
      observer.disconnect();
    }

    // 超时保险(最多等 10 秒)
    setTimeout(() => {
      if (observer) observer.disconnect();
    }, 10000);
  }

  // 页面完全加载后启动
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', observePlayer);
  } else {
    observePlayer();
  }

  // 可选:监听 URL 变化(支持 SPA 切换视频)
  let lastUrl = location.href;
  new MutationObserver(() => {
    const url = location.href;
    if (url !== lastUrl) {
      lastUrl = url;
      setTimeout(observePlayer, 1000); // 延迟等待新播放器加载
    }
  }).observe(document, { subtree: true, childList: true });

})();