Greasy Fork is available in English.
BiliBili倍速插件,支持自定义速度、记忆上一次速度、快捷键调速。
当前为
// ==UserScript==
// @name BiliBili 高级倍速功能
// @namespace cec8225d12878f0fc33997eb79a69894
// @version 1.2
// @description BiliBili倍速插件,支持自定义速度、记忆上一次速度、快捷键调速。
// @author TheBszk
// @match https://www.bilibili.com/video/*
// @match https://www.bilibili.com/list/*
// @match https://www.bilibili.com/bangumi/play/*
// @icon https://www.bilibili.com/favicon.ico
// @license AGPL
// ==/UserScript==
(function () {
"use strict";
const CUSTOM_RATE_ARRAY = "custom_rate_array";
const CUSTOM_RATE = "custom_rate";
function getPageType() {
const path = window.location.pathname;
if (path.startsWith("/video/")) {
return "video";
} else if (path.startsWith("/list/")) {
return "list";
} else if (path.startsWith("/bangumi/play/")) {
return "bangumi";
} else {
return "unknown";
}
}
const pageType = getPageType();
if (pageType == "video" || pageType == "list") {
var MENUCLASS = "bpx-player-ctrl-playbackrate-menu";
var MENUCLASS_ITEM = "bpx-player-ctrl-playbackrate-menu-item";
var MENUCLASS_ACTIVE = "bpx-state-active";
} else if (pageType == "bangumi") {
var MENUCLASS = "squirtle-speed-select-list";
var MENUCLASS_ITEM = "squirtle-select-item";
var MENUCLASS_ACTIVE = "active";
}
function getRate() {
let rate = localStorage.getItem(CUSTOM_RATE);
if (rate <= 0) {
rate = 1;
}
return rate;
}
function getRateArray() {
let storageData = localStorage.getItem(CUSTOM_RATE_ARRAY);
let rates;
if (storageData == null) {
rates = [];
} else {
rates = storageData.split(",");
}
if (rates.length === 0) {
//如果没有,则初始化一个默认的
rates = [0.5, 1.0, 1.5, 2, 2.5, 3.0, 4.0];
localStorage.setItem(CUSTOM_RATE_ARRAY, rates.join(","));
}
return rates;
}
// 创建元素在速度变化时显示
var speedDisplay = document.createElement("div");
speedDisplay.style.display = "none";
speedDisplay.style.position = "absolute";
speedDisplay.style.bottom = "20px";
speedDisplay.style.right = "20px";
speedDisplay.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
speedDisplay.style.color = "white";
speedDisplay.style.padding = "5px";
speedDisplay.style.borderRadius = "5px";
speedDisplay.style.zIndex = "1000";
speedDisplay.style.fontSize = "22px";
let hideTimer;
function showPlayRate(rate) {
speedDisplay.textContent = `速度: ${rate}x`;
speedDisplay.style.display = "block";
if (!hideTimer) {
clearTimeout(hideTimer);
}
hideTimer = setTimeout(function () {
speedDisplay.style.display = "none";
}, 1200);
}
class PlayRateMenu {
init(menu) {
this.menu = menu;
this.rates = getRateArray();
this.videoObj = document.querySelector("video");
this.videoObj.addEventListener("loadedmetadata", () => {
this.setRate(getRate());
});
}
insertRate(rateValue) {
this.rates.push(rateValue);
this.render();
}
insertItem(content, rate, event) {
const item = document.createElement("li");
item.textContent = content;
item.classList.add(MENUCLASS_ITEM);
item.setAttribute("data-value", rate);
item.addEventListener("click", event);
this.menu.appendChild(item);
}
render() {
this.menu.innerHTML = "";
this.rates.sort((a, b) => b - a); //排序
this.rates.forEach((rate) => {
this.insertItem(rate % 1 == 0 ? rate + ".0x" : rate + "x", rate, (e) => {
e.stopPropagation();
const rateValue = e.target.getAttribute("data-value");
this.setVideoRate(rateValue);
this.setActiveRate(rateValue);
localStorage.setItem(CUSTOM_RATE, rateValue);
});
});
//插入一个设置按钮
this.insertItem("设置", 0, (e) => {
e.stopPropagation();
let inputStr = window.prompt("请输入自定义倍速,以英文逗号隔开。", getRateArray().join(","));
if (inputStr === null || inputStr.trim() === "") return;
let rates = inputStr
.split(",")
.map((s) => s.trim())
.filter((s) => s);
if (rates.length === 0) return;
// 检查输入是否全部为有效数字
if (!rates.every((s) => isFinite(s))) {
alert("输入包含无效的倍速值,请输入有效的数字。");
return;
}
localStorage.setItem(CUSTOM_RATE_ARRAY, rates.join(","));
this.rates = rates;
this.render();
let nowRate = getRate();
if (rates.indexOf(nowRate) === -1) {
this.setRate(1);
} else {
this.setRate(nowRate);
}
});
}
setActiveRate(rateValue) {
const items = this.menu.querySelectorAll(`.${MENUCLASS_ITEM}`);
items.forEach((item) => {
const value = item.getAttribute("data-value");
if (value === rateValue) {
item.classList.add(MENUCLASS_ACTIVE);
} else {
item.classList.remove(MENUCLASS_ACTIVE);
}
});
}
setVideoRate(rate) {
this.videoObj.playbackRate = rate;
}
//使用此函数前提:速度列表必须存在该速度值
setRate(rate) {
const item = document.querySelector(`.${MENUCLASS_ITEM}[data-value="${rate}"]`);
if (item) {
item.classList.add(MENUCLASS_ACTIVE);
item.click(); // 模拟点击事件
} else {
console.error("未找到匹配元素");
}
}
changeRate(up) {
let nowRate = getRate();
let index = this.rates.indexOf(nowRate);
if ((index == 0 && up) || (index == this.rates.length && !up)) {
return nowRate;
} else {
index += up ? -1 : 1;
this.setRate(this.rates[index]);
return this.rates[index];
}
}
}
let menu = new PlayRateMenu();
let _interval = setInterval(function () {
let element = document.querySelector(`.${MENUCLASS}`);
if (element) {
document.querySelector(".bpx-player-video-wrap").appendChild(speedDisplay);
menu.init(element);
menu.render();
menu.setRate(getRate());
clearInterval(_interval);
}
}, 500);
let ArrowRightTime = 0;
let OldRate = 0;
document.addEventListener(
"keydown",
function (e) {
e = e || window.event;
if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA" || e.target.isContentEditable) {
return;
}
if (e.ctrlKey == true && e.code == "ArrowUp") {
let rate = menu.changeRate(true);
showPlayRate(rate);
} else if (e.ctrlKey == true && e.code == "ArrowDown") {
let rate = menu.changeRate(false);
showPlayRate(rate);
} else if (e.code == "ArrowRight") {
if (ArrowRightTime == 0) {
ArrowRightTime = e.timeStamp;
} else {
if (e.timeStamp - ArrowRightTime > 500) {
if (OldRate == 0) {
OldRate = getRate();
menu.setVideoRate(OldRate * 2);
showPlayRate(OldRate * 2);
}
}
}
} else if ("0" <= e.key && e.key <= "9") {
e.preventDefault();
e.stopImmediatePropagation();
let num = parseInt(e.key - "0");
if (num == 0) {
num = 0.5;
}
if (e.ctrlKey) {
menu.setVideoRate(num);
menu.setActiveRate(num);
showPlayRate(num);
localStorage.setItem(CUSTOM_RATE, num);
} else {
if (OldRate == 0) {
OldRate = getRate();
menu.setVideoRate(num);
showPlayRate(num);
}
}
}
},
true
);
document.addEventListener("keyup", function (e) {
if (e.code == "ArrowRight" || ("0" <= e.key && e.key <= "9")) {
ArrowRightTime = 0;
if (OldRate != 0) {
menu.setVideoRate(OldRate);
showPlayRate(OldRate);
OldRate = 0;
e.preventDefault();
}
}
});
window.addEventListener("focus", function () {
menu.setRate(getRate());
});
})();