// ==UserScript==
// @name 叶散_B站自定义倍速增强版, 支持快捷键和跨页同步
// @namespace /YBZ/bili-more-rates
// @version 1.2.0
// @description 为 b 站添加更多倍速 (可自定义快捷键),并支持多开页面实时同步速度。
// @author 叶炳之
// @match https://www.bilibili.com/video/*
// @match https://www.bilibili.com/list/watchlater*
// @match https://www.bilibili.com/bangumi/play/*
// @match https://www.bilibili.com/list/*
// @match https://www.bilibili.com/festival/*
// @icon https://www.bilibili.com/favicon.ico
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addValueChangeListener
// @grant GM_registerMenuCommand
// @grant unsafeWindow
// @run-at document-end
// @license MIT
// ==/UserScript==
(function () {
const waitForElement = (selector, callback) => {
const check = () => {
const element = document.querySelector(selector);
if (element) {
callback(element);
} else {
setTimeout(check, 500);
}
};
check();
};
// 显示速度函数
const SPEED_INTERVAL = 1000; // 明确赋值(单位:毫秒)
function showSpeed(speed, index = 1) {
let speedDiv = document.querySelector(`#speed`);
if (!speedDiv) {
const div = document.createElement('div');
div.setAttribute('id', 'speed');
div.innerHTML = '<span></span>';
document.querySelector('.bpx-player-video-area').appendChild(div);
speedDiv = div;
}
let speedSpan = speedDiv.querySelector('span')
if (index == 1) {
speedSpan.innerHTML = `${speed} X`
} else {
speedSpan.innerHTML = `${speed}`
}
speedDiv.style.visibility = 'visible'
clearTimeout(window.speedTimer)
window.speedTimer = setTimeout(() => {
speedDiv.style.visibility = 'hidden'
}, SPEED_INTERVAL)
}
// 主逻辑
const init = () => {
// 自定义速度和快捷键
const myRateAndShortcuts = GM_getValue('myRatesAndShortcuts', [
{ rate: 1.5, shortcut: 'U' },
{ rate: 2, shortcut: 'J' },
{ rate: 2.5, shortcut: 'I' },
{ rate: 3, shortcut: 'K' },
{ rate: 4, shortcut: 'L' },
{ rate: 5, shortcut: ';' },
]);
// 存储键名
const CUSTOM_RATE = 'custom_rate';
// 获取当前倍速(优先从 localStorage 读取,实时性更高)
const getRate = () => {
const localRate = parseFloat(localStorage.getItem(CUSTOM_RATE));
const gmRate = GM_getValue(CUSTOM_RATE, 1);
return localRate || gmRate;
};
// 设置倍速并同步到所有存储
const setRate = (rate) => {
GM_setValue(CUSTOM_RATE, rate);
localStorage.setItem(CUSTOM_RATE, rate);
};
// 自定义速度和快捷键设置
const customRatesAndShortcuts = (defaultValue) => {
const arr2str = (arr) => arr.map(item => `${item.rate}(${item.shortcut})`).join(',');
const str2arr = (str) => {
return str.split(',').map(item => {
const match = item.match(/^(\d+(\.\d+)?)\((\S)\)$/i);
return match ? { rate: parseFloat(match[1]), shortcut: match[3] } : null;
}).filter(Boolean);
};
const input = prompt(
'格式: 倍数1(快捷键),倍数2(快捷键)\n例如: 2(J),3(K),4(L)',
arr2str(defaultValue)
);
if (input) {
const newRates = str2arr(input);
if (newRates.length > 0) {
GM_setValue('myRatesAndShortcuts', newRates);
alert('已保存,刷新页面生效!');
}
}
};
// 注册菜单命令
GM_registerMenuCommand("自定义倍数和快捷键", () => customRatesAndShortcuts(myRateAndShortcuts));
// 获取视频和倍速菜单元素
const domVideo = document.querySelector('.bpx-player-video-wrap video') || document.querySelector('bwp-video');
const domRateMenu = document.querySelector('.bpx-player-ctrl-playbackrate-menu');
if (!domVideo || !domRateMenu) return;
// 添加自定义倍速选项(只显示≤4倍速)
const existRates = [2, 1.5, 1.25, 1, 0.75, 0.5];
myRateAndShortcuts.forEach(({ rate }) => {
if (!existRates.includes(rate) && rate <= 4) {
const item = document.createElement('li');
item.className = 'bpx-player-ctrl-playbackrate-menu-item';
item.textContent = `${rate % 1 === 0 ? rate + '.0' : rate}x`;
item.dataset.value = rate;
item.addEventListener('click', () => {
domVideo.playbackRate = rate;
setRate(rate);
showSpeed(rate); // 点击菜单项也显示提示
});
domRateMenu.prepend(item);
}
});
// 监听原生倍速选项的点击
const originalRateItems = domRateMenu.querySelectorAll('.bpx-player-ctrl-playbackrate-menu-item');
originalRateItems.forEach(item => {
item.addEventListener('click', () => {
const rate = parseFloat(item.dataset.value);
setRate(rate);
showSpeed(rate); // 点击原生菜单项也显示提示
});
});
// 初始化倍速
domVideo.playbackRate = getRate();
// 监听其他页面的倍速修改
GM_addValueChangeListener(CUSTOM_RATE, (key, oldVal, newVal) => {
domVideo.playbackRate = parseFloat(newVal);
});
// 快捷键监听
unsafeWindow.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
myRateAndShortcuts.forEach(({ rate, shortcut }) => {
if (shortcut && key === shortcut.toLowerCase()) {
domVideo.playbackRate = rate;
setRate(rate);
showSpeed(rate); // 快捷键设置时显示提示
e.preventDefault();
}
});
});
};
// 等待播放器加载完成
waitForElement('.bpx-player-ctrl-playbackrate-menu', init);
})();