您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
让播放器在AB点之间循环!
当前为
// ==UserScript== // @name 哔哩哔哩AB循环 // @namespace ckylin-script-bilibili-abloop // @version 0.5 // @description 让播放器在AB点之间循环! // @author CKylinMC // @match https://www.bilibili.com/video/* // @grant unsafeWindow // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_getValue // @grant GM_setValue // @license GPLv3 License // ==/UserScript== (function() { 'use strict'; //if (!('ABLOOPDEBUG' in unsafeWindow)) unsafeWindow.ABLOOPDEBUG = false; const get = q => document.querySelector(q); const wait = t => new Promise(r => setTimeout(r, t)); const log = (...m) => console.log('[ABLoop]', ...m); //const d = (...m) => unsafeWindow.ABLOOPDEBUG ? console.log('[ABLoop Debug]', ...m) : 0; const registerMenu = (text, callback) => menuIds.push(GM_registerMenuCommand(text, callback)); const clearMenu = () => { menuIds.forEach(id => GM_unregisterMenuCommand(id)); menuIds = []; }; const getTotalTime = async () => await waitForAttribute(cfg.video,'duration'); const getCurrentTime = () => cfg.video.currentTime; const setTime = t => unsafeWindow.player.seek(t); const play = () => unsafeWindow.player.play(); const pause = () => unsafeWindow.player.pause(); const cfg = { a: 0, b: 999, video: null, isLooping: false, listener: () => getCurrentTime() >= (cfg.b-0.2) ? setTime(cfg.a) : 0 } const guibar = { toBar: null, fromBar:null } let menuIds = []; let menus = {}; async function playerReady(){ let i=50; while(--i>=0){ await wait(200); if(!('player' in unsafeWindow)) continue; if(!('isInitialized' in unsafeWindow.player)) continue; if(!unsafeWindow.player.isInitialized()) continue; return true; } return false; } async function waitForDom(q) { let i = 50; let dom; while (--i >= 0) { if (dom = get(q)) break; await wait(100); } return dom; } async function waitForAttribute(q, attr) { let i = 50; let value; while (--i >= 0) { if ((attr in q) && q[attr] != null) { value = q[attr]; break; } await wait(100); } return value; } function applyMenus() { clearMenu(); for (let item in menus) { if(!menus.hasOwnProperty(item)) continue; let menu = menus[item]; registerMenu(menu.text, menu.callback); } } function setMenu(id,text,callback,noapply = false) { menus[id] = { text, callback }; if (!noapply) applyMenus(); } function triggerAPoint() { cfg.a = getCurrentTime(); //d('getCurrentTime', getCurrentTime()); setFromBarPos(); setAPointMenu(); } function triggerBPoint() { cfg.b = getCurrentTime(); //d('getCurrentTime', getCurrentTime()); setToBarPos(); setBPointMenu(); } function triggerToggleDoStop(fast=false) { if(!fast)cfg.isLooping = !cfg.isLooping; cfg.video.removeEventListener('timeupdate',cfg.listener); pause(); if(!fast)hideBars(); if(!fast)setLoopListenerMenu(false); if(!fast)forgiveAllPoint() } function triggerToggleDoStart(autostart=true) { triggerToggleDoStop(true); cfg.isLooping = !cfg.isLooping; cfg.video.addEventListener('timeupdate',cfg.listener); setTime(cfg.a); if(autostart)play(); showBars(); setLoopListenerMenu(false); saveAllPoint() } function triggerToggleDoAuto() { if (cfg.isLooping) { triggerToggleDoStop(); } else { triggerToggleDoStart(); } } function setAPointMenu(noapply = false) { setMenu("APOINT", "设置A点 (当前A点:" + (Math.floor(cfg.a*100)/100) + ")", triggerAPoint, noapply); } function setBPointMenu(noapply = false) { setMenu("BPOINT", "设置B点 (当前B点:" + (Math.floor(cfg.b*100)/100) + ")", triggerBPoint, noapply); } function setSavePointMenu(noapply = false) { setMenu("SAVEPOINT", "记住此页循环设置", saveAllPoint, noapply); } function setForgivePointMenu(noapply = false) { setMenu("SAVEPOINT", "清除此页循环设置", forgiveAllPoint, noapply); } function setLoopListenerMenu(noapply = false) { if (cfg.isLooping) { setMenu("LOOP", "停止循环", triggerToggleDoStop, noapply); } else { setMenu("LOOP", "开始循环", triggerToggleDoStart, noapply); } } function removeDom(...qs){ qs.forEach(q=>{ if (q) { let target; if (q instanceof Element) target = q; else target = document.querySelectorAll(q); if(target&&target.length){ target.forEach(e=>e.remove()); } } }); } function newBar() { let bar = document.createElement("div"); bar.classList.add("bui-bar"); bar.classList.add("abloop-custombar"); bar.style.transform = "scaleX(0)"; return bar; } function addStyleOnce(id,css) { let style = document.querySelector("#abloop-css-" + id); if (style) return; style = document.createElement("style"); style.id = "abloop-css-" + id; style.innerHTML = css; document.body.appendChild(style); return; } async function setAPointBarPos(){ let point = cfg.a; let duration = await getTotalTime(); let dt = point/duration; if (!guibar.fromBar) await createMarkBar(); const playbar = await waitForDom(".bui-bar.bui-bar-normal"); if (!playbar) return; guibar.fromBar.style.transform = `scaleX(${dt})`; showBarA(); } async function setBPointBarPos(){ let point = cfg.b; let duration = await getTotalTime(); let dt = point/duration; if (!guibar.toBar) await createMarkBar(); const playbar = await waitForDom(".bui-bar.bui-bar-normal"); if (!playbar) return; guibar.toBar.style.transform = `scaleX(${dt})`; showBarA(); } async function setFromBarPos() { if (!guibar.fromBar) await createMarkBar(); const playbar = await waitForDom(".bui-bar.bui-bar-normal"); if (!playbar) return; guibar.fromBar.style.transform = playbar.style.transform; showBarA(); } async function setToBarPos() { if (!guibar.toBar) await createMarkBar(); const playbar = await waitForDom(".bui-bar.bui-bar-normal"); if (!playbar) return; guibar.toBar.style.transform = playbar.style.transform; showBarB() } function showBars() { let bars = document.querySelectorAll(".abloop-custombar"); bars.forEach(bar => { if (!bar.classList.contains("show")) bar.classList.add("show"); }) } function showBarA() { if (guibar.fromBar && !guibar.fromBar.classList.contains("show")) guibar.fromBar.classList.add("show"); } function showBarB() { if (guibar.toBar && !guibar.toBar.classList.contains("show")) guibar.toBar.classList.add("show"); } function hideBars() { let bars = document.querySelectorAll(".abloop-custombar"); bars.forEach(bar => { if (bar.classList.contains("show")) bar.classList.remove("show"); }) } async function createMarkBar(){ removeDom(guibar.fromBar, guibar.toBar); const playbar = await waitForDom(".bui-bar.bui-bar-normal"); if (!playbar) return; addStyleOnce('markbar', ` .abloop-custombar{ opacity: 0; transform: scale(0); } .abloop-custombar.show{ opacity: 1!important; transition: transform ease .3s,opacity .2s; } `); guibar.fromBar = newBar(); guibar.fromBar.style.background = "#9e9e9e"; guibar.fromBar.style.backgroundColor = "#9e9e9e"; guibar.fromBar.style.transform = "scale(0)"; guibar.fromBar.setAttribute('style', 'background:#9e9e9e!important'); playbar.parentNode.appendChild(guibar.fromBar, playbar); guibar.toBar = newBar(); guibar.toBar.style.background = "#8bc34a"; guibar.toBar.style.backgroundColor = "#8bc34a"; guibar.toBar.style.transform = "scale(1)"; guibar.toBar.setAttribute('style', 'background:#8bc34a!important'); playbar.parentNode.insertBefore(guibar.toBar, playbar); } function hotKeyHandler(e) { log(1,e); if (['KeyA', 'KeyB', 'KeyO'].includes(e.code)) { log(2,e); if (e.ctrlKey || e.altKey || e.shiftKey) return; if ([...e.path.filter(t => t.tagName == "TEXTAREA" || t.tagName == "INPUT")].length) return; switch (e.key) { case "a": triggerAPoint(); e.preventDefault(); break; case "b": triggerBPoint(); e.preventDefault(); break; case "o": triggerToggleDoAuto(); e.preventDefault(); break; } } } function regHotKey() { unsafeWindow.removeEventListener('keypress', hotKeyHandler); unsafeWindow.addEventListener('keypress', hotKeyHandler); } function str2float(str,fallback=-1){ try{ let num = parseFloat(str); if(isNaN(num)) return fallback; if(typeof(num)==='undefined') return fallback; return num; }catch(e){ return fallback; } } function forgiveAllPoint(){ GM_setValue(`a:${location.pathname}`,-1); GM_setValue(`b:${location.pathname}`,-1); setSavePointMenu(); } function saveAllPoint(){ saveAPoint(); saveBPoint(); setForgivePointMenu(); } function saveAPoint(){ GM_setValue(`a:${location.pathname}`,cfg.a); } function saveBPoint(){ GM_setValue(`b:${location.pathname}`,cfg.b); } function getAPoint(){ return str2float(GM_getValue(`a:${location.pathname}`)); } function getBPoint(){ return str2float(GM_getValue(`b:${location.pathname}`)); } async function loadFromSavedData(){ let a = getAPoint(); let b = getBPoint(); let loopauto = false; if(a>=0){ cfg.a = a; setAPointBarPos(); setAPointMenu(false); loopauto=true; } if(b>=0){ cfg.b = Math.min(b,await getTotalTime()); setBPointBarPos(); setBPointMenu(false); loopauto=true; } if(loopauto) { setForgivePointMenu(); triggerToggleDoStart(false); } return loopauto; } async function loadFromURL(){ let url = new URL(location.href); let a = str2float(url.searchParams.get('ta')); let b = str2float(url.searchParams.get('tb')); let loopauto = false; if(a>=0){ cfg.a = a; saveAPoint(); setAPointBarPos(); setAPointMenu(false); loopauto=true; } if(b>=0){ cfg.b = Math.min(b,await getTotalTime()); saveBPoint(); setBPointBarPos(); setBPointMenu(false); loopauto=true; } if(loopauto) { setForgivePointMenu(); triggerToggleDoStart(false); } return loopauto; } async function init() { log("Waiting for player to be ready..."); if(!(await playerReady())) return log("No player found on this page."); cfg.video = await waitForDom(".bilibili-player-video video"); //d('video', get(".bilibili-player-video video")); //d('total', await getTotalTime()); cfg.video = get(".bilibili-player-video video"); cfg.b = (await getTotalTime())-0.1; setAPointMenu(true); setBPointMenu(true); setLoopListenerMenu(true); setSavePointMenu(); await createMarkBar(); regHotKey(); log("Initialization OK"); if((await loadFromSavedData())+(await loadFromURL())) showBars(); } init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址