// ==UserScript== // @name BB平台-题包全量预加载(走帧不卡) // @namespace bb-preload // @version 1.0.0 // @description BB(Baker)平台 CV 编辑器:题包打开后自动把全部帧图片预加载完,走帧不再逐帧卡顿。原理:把平台的按需加载(LOAD_ON_DEMAND,前后各5帧)改为一次性全量加载。 // @author Bro // @match http://14.103.192.72:18778/app/editor/* // @match https://cvpc.data-baker.com/app/editor/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; // 幂等:同一页面只初始化一次(防脚本被重复注入) if (window.__bbPreloadInstalled) { try { console.log('[预加载] 已初始化过,跳过重复注入'); } catch (e) { } return; } window.__bbPreloadInstalled = true; var CFG = { pollMs: 150, // 等待编辑器的轮询间隔 waitTimeoutMs: 60000, // 等编辑器就绪的最长时间 settleMs: 500, // 每轮检查间隔 maxWaitMs: 180000, // 全量加载最长等待 batchSize: 12 // 兜底补载时每轮最多处理几帧 }; var loadedKey = ''; // 已预加载过的题包标识 var running = false; function log(msg) { try { console.log('[预加载] ' + msg); } catch (e) { } } function sleep(ms) { return new Promise(function (r) { setTimeout(r, ms); }); } function getEditor() { return window.__cvpc_editor__ || window.$$__GLOBAL__EDITOR__CORE__$$ || null; } function getFsc() { var e = getEditor(); var fsc = e && e.frameSequenceController; if (!fsc) return null; var frames = fsc.frames; if (!frames || !frames.length) return null; return fsc; } // 当前题包标识(换包会变) function packKey() { try { var q = new URLSearchParams(location.search); return (q.get('data_id') || '') + '|' + (q.get('job_id') || '') + '|' + (q.get('task_id') || ''); } catch (e) { return location.href; } } /* ---------------- 小浮层 ---------------- */ function ui(text, show, autoHideMs) { var el = document.getElementById('bbpl-chip'); if (!el) { if (!document.body) return; el = document.createElement('div'); el.id = 'bbpl-chip'; el.style.cssText = 'position:fixed;right:14px;bottom:14px;z-index:2147483000;' + 'background:rgba(23,26,31,.86);color:#e8eaed;font:12px/1.5 "Microsoft YaHei",system-ui,sans-serif;' + 'padding:5px 12px;border-radius:14px;box-shadow:0 4px 14px rgba(0,0,0,.22);' + 'transition:opacity .45s;pointer-events:none;opacity:0;letter-spacing:.2px;'; document.body.appendChild(el); } el.textContent = text; el.style.opacity = '1'; if (autoHideMs) setTimeout(function () { if (el) el.style.opacity = '0'; }, autoHideMs); } /* ---------------- 主流程 ---------------- */ async function preload() { if (running) return; running = true; var key = packKey(); try { // 1) 等编辑器与帧数据就绪 var fsc = null; var t0 = Date.now(); while (Date.now() - t0 < CFG.waitTimeoutMs) { fsc = getFsc(); if (fsc) break; await sleep(CFG.pollMs); } if (!fsc) { log('等待编辑器超时(' + CFG.waitTimeoutMs + 'ms),未找到帧数据'); return; } var total = fsc.frames.length; loadedKey = key; // 已经全部就绪就不用再折腾 var ready0 = 0; for (var r0 = 0; r0 < total; r0++) { try { if (fsc.frames[r0].state && fsc.frames[r0].state.assetsReady) ready0++; } catch (e) { } } if (ready0 === total) { log('全部 ' + total + ' 帧已就绪,无需加载'); return; } log('题包 ' + key + ' 共 ' + total + ' 帧(当前就绪 ' + ready0 + '),开始全量预加载'); ui('预加载 ' + ready0 + '/' + total); // 2) 调用平台自带的全量加载(等价于把 loadMode 设为 LOAD_ALL) try { fsc.setupAssetLoadAll(); log('已调用 setupAssetLoadAll()'); } catch (e) { log('setupAssetLoadAll 调用失败:' + (e && e.message || e)); } // 3) 兜底:轮询未就绪的帧,补一次 prepare + load var start = Date.now(); var lastReady = -1; while (Date.now() - start < CFG.maxWaitMs) { await sleep(CFG.settleMs); var ready = 0, pending = []; for (var i = 0; i < fsc.frames.length; i++) { var f = fsc.frames[i]; var ok = false; try { ok = !!(f && f.state && f.state.assetsReady); } catch (e) { ok = false; } if (ok) ready++; else pending.push(f); } ui('预加载 ' + ready + '/' + total); if (!pending.length) break; if (ready === lastReady && pending.length === total) { // 完全没进展,再催一次平台接口 try { fsc.setupAssetLoadAll(); } catch (e) { } } lastReady = ready; // 补载一小批,避免瞬时并发过高 for (var j = 0; j < pending.length && j < CFG.batchSize; j++) { var fr = pending[j]; if (!fr) continue; try { if (fr.prepare) await fr.prepare(); if (fr.load) fr.load({ image: { isPrimary: true, isHighPriority: false } }); } catch (e) { } } } var doneCnt = 0; for (var k = 0; k < fsc.frames.length; k++) { try { if (fsc.frames[k].state && fsc.frames[k].state.assetsReady) doneCnt++; } catch (e) { } } log('预加载结束:' + doneCnt + '/' + total + ' 帧就绪'); ui('预加载完成 ' + doneCnt + '/' + total, true, 2500); } catch (e) { log('预加载异常:' + (e && e.message || e)); } finally { running = false; } } /* ---------------- 启动 & 换包监听 ---------------- */ function boot() { var lastKey = packKey(); preload(); // SPA 内换题包时重新预加载 setInterval(function () { var k = packKey(); if (k !== lastKey) { lastKey = k; log('检测到切换题包:' + k); setTimeout(preload, 800); } }, 1000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } // 控制台手动触发 window.__bbPreload = { run: function () { loadedKey = ''; preload(); } }; })();