您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
BiliBili倍速插件,支持自定义速度、记忆上一次速度、快捷键调速。
当前为
// ==UserScript== // @name BiliBili 高级倍速功能 // @namespace cec8225d12878f0fc33997eb79a69894 // @version 1.3 // @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"; const CUSTOM_ShowTimeState = "custom_showtimestate"; 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; } // 创建显示元素 function createTip() { var elem = document.createElement("div"); elem.style.display = "none"; elem.style.position = "absolute"; elem.style.backgroundColor = "rgba(255, 255, 255, 0.2)"; elem.style.color = "white"; elem.style.padding = "5px"; elem.style.borderRadius = "5px"; elem.style.zIndex = "1000"; elem.style.fontSize = "22px"; return elem; } var timeDisplay = createTip(); timeDisplay.style.top = "20px"; timeDisplay.style.right = "20px"; let _showtime; function setShowTimeState(state) { localStorage.setItem(CUSTOM_ShowTimeState, state); if (state == true) { timeDisplay.style.display = "block"; if (!_showtime) { _showtime = setInterval(FlashShowTime, 1000); } } else { timeDisplay.style.display = "none"; if (_showtime) { clearInterval(_showtime); _showtime = 0; } } } var speedDisplay = createTip(); speedDisplay.style.bottom = "20px"; speedDisplay.style.right = "20px"; 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.videoObj = document.querySelector("video"); if (!this.videoObj) { this.videoObj = document.querySelector("bwp-video"); } if (!this.videoObj) { return false; } this.menu = menu; this.rates = getRateArray(); this.videoObj.addEventListener("loadedmetadata", () => { this.setRate(getRate()); }); return true; } 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); } }); } getDuration() { return this.videoObj.duration; } getCurrentTime() { return this.videoObj.currentTime; } setVideoRate(rate) { this.videoObj.playbackRate = parseFloat(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) { if (menu.init(element)) { menu.render(); menu.setRate(getRate()); document.querySelector(".bpx-player-video-wrap").appendChild(speedDisplay); document.querySelector(".bpx-player-video-wrap").appendChild(timeDisplay); setShowTimeState(localStorage.getItem(CUSTOM_ShowTimeState) == "true"); clearInterval(_interval); } else { console.warn("获取视频元素失败!"); } } }, 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(); } } else if (e.code == "F2") { setShowTimeState(localStorage.getItem(CUSTOM_ShowTimeState) == "false"); } }); window.addEventListener("focus", function () { menu.setRate(getRate()); setShowTimeState(localStorage.getItem(CUSTOM_ShowTimeState)); }); function formatTime(s) { var m = parseInt(s / 60); var ss = parseInt(s % 60); return (m > 9 ? `${m}` : `0${m}`) + ":" + (ss > 9 ? `${ss}` : `0${ss}`); } function FlashShowTime() { var rate = getRate(); timeDisplay.textContent = formatTime(menu.getCurrentTime() / rate) + "/" + formatTime(menu.getDuration() / rate); } })();