您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
稳定可靠的AB点循环工具,适配最新B站页面结构
当前为
// ==UserScript== // @name B站循环助手-稳定版 // @namespace bilibili-replayer // @version 1.11 // @description 稳定可靠的AB点循环工具,适配最新B站页面结构 // @author dms // @match https://www.bilibili.com/video/BV* // @match https://www.bilibili.com/bangumi/play/ep* // @match https://www.bilibili.com/medialist/play/* // @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // ==/UserScript== (function() { 'use strict'; // 存储管理 const Storage = { savePoint: (index, value) => { try { GM_setValue(`Point_${index}`, value); return true; } catch(e) { console.error('保存点位失败:', e); return false; } }, getPoint: (index) => { try { return GM_getValue(`Point_${index}`, null); } catch(e) { console.error('获取点位失败:', e); return null; } } }; // 工具函数 const Utils = { isNormalVideo: /^(https?:\/\/(www\.)bilibili\.com\/video\/(?:BV|AV)\w+).*/i.test(window.location.href), async copyText(text) { try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return true; } } catch(e) { console.warn('现代复制API失败,使用传统方法'); } try { const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); textArea.remove(); return true; } catch(e) { console.error('复制失败:', e); return false; } }, createButton(text, className, parent) { const button = document.createElement('div'); className.split(' ').forEach(c => button.classList.add(c)); button.innerText = text; parent.appendChild(button); return button; }, showNotification(text, title = '提示', timeout = 2000) { GM_notification({ text, title, timeout }); } }; class VideoController { constructor(video) { this.video = video; this.points = [0, video.duration-1]; this.pointButtons = []; this.animationFrameId = null; this.lastTime = 0; } setPoint(index, value) { if (this.pointButtons[index].classList.contains('active-button')) { this.points[index] = index ? this.video.duration-1 : 0; this.pointButtons[index].classList.remove('active-button'); Storage.savePoint(index, null); } else { this.points[index] = value; this.pointButtons[index].classList.add('active-button'); Storage.savePoint(index, this.points[index]); } } startLoop(button) { if(this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; button.classList.remove('active-button'); button.innerText = '▶循环'; return; } button.classList.add('active-button'); button.innerText = '■停止'; const checkLoop = (timestamp) => { if (timestamp - this.lastTime > 200) { const A = this.points[0] <= this.points[1] ? this.points[0] : this.points[1]; const B = this.points[0] > this.points[1] ? this.points[0] : this.points[1]; if(this.video.currentTime >= B) { this.video.currentTime = A; } this.lastTime = timestamp; } this.animationFrameId = requestAnimationFrame(checkLoop); }; this.animationFrameId = requestAnimationFrame(checkLoop); } async getTimeLink(time) { const link = window.location.href.replace(/^(https?:\/\/(www\.)bilibili\.com\/video\/(?:BV|AV)\w+).*/i, '$1') + '?t=' + time; const success = await Utils.copyText(link); Utils.showNotification( success ? '时间标记链接已复制到剪切板' : '复制失败,请手动复制', success ? '复制成功' : '复制失败' ); } } const createToolbar = async () => { const video = document.querySelector('#bilibili-player video'); if (!video) return; const controller = new VideoController(video); const waitForControl = setInterval(() => { const controlBar = document.querySelector('.bpx-player-control-bottom'); if (!controlBar) return; clearInterval(waitForControl); const toolbarbox = document.createElement('div'); toolbarbox.style = ` display: flex; align-items: center; height: 100%; padding: 0 10px; position: relative; z-index: 100; `; const progressBar = controlBar.querySelector('.bpx-player-control-wrap'); if (progressBar && progressBar.nextSibling) { controlBar.insertBefore(toolbarbox, progressBar.nextSibling); } else { controlBar.appendChild(toolbarbox); } const toolbarShadow = toolbarbox.attachShadow({mode: 'closed'}); const toolbarStyle = document.createElement('style'); toolbarStyle.innerHTML = ` #replayer-toolbar { display: flex; align-items: center; height: 100%; font-size: 0.7rem; color: #ffffff; } .tool-item { padding: 0 0.25rem; margin: 0 1px; border-radius: .2rem; color: #ffffff; } .tool-button { cursor: pointer; opacity: 0.85; transition: all 0.2s ease; } .tool-button:not(.active-button):hover { opacity: 1; background-color: rgba(255, 255, 255, 0.1); } .active-button { background-color: #00a1d6; color: white; opacity: 1; } .active-button:hover { background-color: #00a1d6; opacity: 1; } .hide { display: none; } `; toolbarShadow.appendChild(toolbarStyle); const toolbar = document.createElement('div'); toolbar.id = 'replayer-toolbar'; toolbarShadow.appendChild(toolbar); const getLinkClass = Utils.isNormalVideo ? '' : ' hide'; const pointA = Utils.createButton('起点A', 'tool-item tool-button', toolbar); const toA = Utils.createButton('跳A', 'tool-item tool-button', toolbar); const linkA = Utils.createButton('复制链接', 'tool-item tool-button' + getLinkClass, toolbar); Utils.createButton('|', 'tool-item tool-text', toolbar); const pointB = Utils.createButton('终点B', 'tool-item tool-button', toolbar); const toB = Utils.createButton('跳B', 'tool-item tool-button', toolbar); const linkB = Utils.createButton('复制链接', 'tool-item tool-button' + getLinkClass, toolbar); Utils.createButton('|', 'tool-item tool-text', toolbar); const Start = Utils.createButton('▶循环', 'tool-item tool-button', toolbar); Utils.createButton('|', 'tool-item tool-text' + getLinkClass, toolbar); Utils.createButton('当前:', 'tool-item tool-text' + getLinkClass, toolbar); const linkNow = Utils.createButton('复制链接', 'tool-item tool-button' + getLinkClass, toolbar); controller.pointButtons = [pointA, pointB]; pointA.addEventListener('click', () => { controller.setPoint(0, video.currentTime); }); pointB.addEventListener('click', () => { controller.setPoint(1, video.currentTime); }); Start.addEventListener('click', () => controller.startLoop(Start)); toA.addEventListener('click', () => { video.currentTime = controller.points[0]; }); toB.addEventListener('click', () => { video.currentTime = controller.points[1]; }); linkA.addEventListener('click', () => controller.getTimeLink(controller.points[0])); linkB.addEventListener('click', () => controller.getTimeLink(controller.points[1])); linkNow.addEventListener('click', () => controller.getTimeLink(video.currentTime)); }, 1000); }; if (document.readyState === 'complete') { createToolbar(); } else { window.addEventListener('load', createToolbar); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址