您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
BiliBili倍速插件,支持自定义速度、记忆上一次速度、快捷键调速。
当前为
// ==UserScript== // @name BiliBili 高级倍速功能 // @namespace cec8225d12878f0fc33997eb79a69894 // @version 1.5 // @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"; const CUSTOM_ArrowRightSpeed = "custom_arrowrightspeed"; if (!localStorage.getItem(CUSTOM_ArrowRightSpeed)) { localStorage.setItem(CUSTOM_ArrowRightSpeed, "2x"); //设置默认值 } 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" || pageType == "bangumi") { var MENUCLASS = "bpx-player-ctrl-playbackrate-menu"; var MENUCLASS_ITEM = "bpx-player-ctrl-playbackrate-menu-item"; var MENUCLASS_ACTIVE = "bpx-state-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 SettingPopup { popup_dragend_move(e) { this.popup.style.left = e.clientX - this.offsetX + this.startX + "px"; this.popup.style.top = e.clientY - this.offsetY + this.startY + "px"; } constructor() { this.speedlist = getRateArray().join(","); this.ArrowRightTime = localStorage.getItem(CUSTOM_ArrowRightSpeed); } create(handle) { this.popup = document.createElement("div"); this.popup.innerHTML = ` <div class="popup-title" id="popupTitle"> <span>BiliBili 高级倍速功能</span> <button class="close-button">×</button> </div> <div class="popup-content"> <label for="speedList">自定义倍速列表:</label> <input type="text" id="speedList" placeholder="以英文逗号隔开" /> <label for="arrowRightSpeed">长按右光标键速度:</label> <input type="text" id="arrowRightSpeed" placeholder="例: 2 为固定二倍速, 2x 为当前速度两倍" /> <label><input type="checkbox" disabled checked />增加快捷键: 字幕切换(Z)</label> <br /> <label><input type="checkbox" disabled checked />增加快捷键: 网页全屏(G)</label> <br /> <label><input type="checkbox" disabled checked />双击字幕复制内容</label> </div> <div id="popup-tips">关闭设置窗口自动保存</div> `; this.popup.classList.add("popup-container"); this.popupcss = document.createElement("style"); this.popupcss.innerHTML = ` .popup-container { width: 330px; position: absolute; z-index: 999999; background-color: #fff; border: 1px solid #ccc; border-radius: 8px; box-shadow: 0 0 10px rgba(33,150,243,0.5); } .popup-container .popup-title { position: relative; background-color: #3498db; color: #fff; padding: 10px; cursor: move; border-top-left-radius: 8px; border-top-right-radius: 8px; user-select: none; } .popup-container .popup-content { padding: 20px; } .popup-container .close-button { position: absolute; top: 0px; right: 0px; height: 100%; background-color: #3498db; color: #fff; border: none; padding: 0px 13px; font-size: 24px; cursor: pointer; border-top-right-radius: 8px; transition: background-color 0.3s ease, transform 0.3s ease; } .popup-container .close-button:hover { background-color: #e74c3c; } .popup-container label { font-size: 14px; } .popup-container #popup-tips { color: #555555; font-size: 14px; padding: 4px 0px 4px 10px; border-top: 1px solid #ccc; } .popup-container .button { display: block; padding: 10px; background-color: #3498db; color: #fff; text-align: center; text-decoration: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s ease; border: none; } .popup-container .button:hover { background-color: #2980b9; } .popup-container select, input[type="text"] { display: block; margin-bottom: 10px; padding: 8px; border: 1px solid #ccc; border-radius: 4px; width: 100%; box-sizing: border-box; outline: none; } .popup-container input[type="text"]:focus { border: 1px solid #2980b9; } .popup-container input[type="radio"] { margin-right: 5px; }`; document.body.appendChild(this.popup); document.head.appendChild(this.popupcss); this.popup_dragend_move = this.popup_dragend_move.bind(this); this.popup.querySelector("#popupTitle").addEventListener("mousedown", (e) => { this.offsetX = e.clientX; this.offsetY = e.clientY; this.startX = parseInt(this.popup.style.left); this.startY = parseInt(this.popup.style.top); document.addEventListener("mousemove", this.popup_dragend_move); document.addEventListener("mouseup", (e) => { document.removeEventListener("mousemove", this.popup_dragend_move); }); }); this.popup.querySelector(".close-button").addEventListener("click", (e) => { this.close(); }); this.handle = handle; } show() { this.popup.querySelector("#speedList").value = this.speedlist; this.popup.querySelector("#arrowRightSpeed").value = this.ArrowRightTime; this.popup.style.display = "block"; let left = (window.innerWidth - this.popup.offsetWidth) / 2; let top = (window.innerHeight - this.popup.offsetHeight) / 2; this.popup.style.left = left + "px"; this.popup.style.top = top + "px"; } close() { let sl, ars; // 读取元素的值 sl = this.popup.querySelector("#speedList").value; ars = this.popup.querySelector("#arrowRightSpeed").value; let sl_ = null, ars_ = null; //进行处理 //自定义速度列表 if (!(sl === null || sl.trim() === "")) { let rates = sl .split(",") .map((s) => s.trim()) .filter((s) => s); if (rates.length > 0) { // 检查输入是否全部为有效数字 if (rates.every((s) => isFinite(s))) { localStorage.setItem(CUSTOM_RATE_ARRAY, rates.join(",")); this.speedlist = sl; sl_ = rates; } } } //右光标键速度 if (parseInt(ars) > 0) { localStorage.setItem(CUSTOM_ArrowRightSpeed, ars); this.ArrowRightTime = ars; ars_ = ars; } this.handle(sl_, ars_); this.popup.remove(); } } let setting = new SettingPopup(); class PlayRateMenu { init(menu) { this.videoObj = document.querySelector("video"); this.saveSetting = this.saveSetting.bind(this); 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); } saveSetting(sl, ars) { if (sl != null) { this.rates = sl; this.render(); let nowRate = getRate(); if (this.rates.indexOf(nowRate) === -1) { this.setRate(1); } else { this.setRate(nowRate); } } } 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(); setting.create(this.saveSetting); setting.show(); }); } 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); } getVideoRate() { return this.videoObj.playbackRate; } //使用此函数前提:速度列表必须存在该速度值 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("获取视频元素失败!"); } //双击复制字幕内容 let subtitle_panel = document.querySelector(".bpx-player-subtitle-panel-major-group"); if (subtitle_panel) { subtitle_panel.addEventListener("dblclick", function () { let text = document.querySelector(".bpx-player-subtitle-panel-major-group span").textContent; //如果是歌词会存在音乐符号,要清除 let musicSymbol = "♪"; if (text.startsWith(musicSymbol)) { text = text.slice(musicSymbol.length); if (text.endsWith(musicSymbol)) { text = text.slice(0, -musicSymbol.length); } } navigator.clipboard.writeText(text); }); } } }, 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(); if (typeof setting.ArrowRightTime === "string" && setting.ArrowRightTime.indexOf("x") != -1) { menu.setVideoRate(OldRate * parseInt(setting.ArrowRightTime)); showPlayRate(OldRate * parseInt(setting.ArrowRightTime)); } else { menu.setVideoRate(parseInt(setting.ArrowRightTime)); showPlayRate(parseInt(setting.ArrowRightTime)); } } } } } 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); } } } else if (e.key == "z") { let subtitle_btn = document.querySelector("#bilibili-player .bpx-player-ctrl-subtitle span"); if (subtitle_btn) { subtitle_btn.click(); } } else if (e.key == "g") { document.querySelector("#bilibili-player .bpx-player-ctrl-web span").click(); } }, 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) == "true"); }); 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 = menu.getVideoRate(); timeDisplay.textContent = formatTime(menu.getCurrentTime() / rate) + "/" + formatTime(menu.getDuration() / rate); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址