// ==UserScript== // @name B站视频优先加载 · 先视频后其他 // @name:zh-CN B站视频优先加载 · 先视频后其他 // @namespace https://github.com/example/bilibili-fast-video // @version 2.0.0 // @description 打开B站视频页时先把带宽和连接让给视频流:评论/推荐/直播/弹幕等非必要请求暂缓发起(不屏蔽),待视频开始播放或缓冲充分后自动开始加载,兼顾视频流畅与页面完整。可用 localStorage 配置。 // @description:zh-CN 打开B站视频页时先把带宽和连接让给视频流:评论/推荐/直播/弹幕等非必要请求暂缓发起(不屏蔽),待视频开始播放或缓冲充分后自动开始加载,兼顾视频流畅与页面完整。可用 localStorage 配置。 // @author you // @license MIT // @match https://www.bilibili.com/video/* // @match https://www.bilibili.com/bangumi/play/* // @match https://www.bilibili.com/list/* // @match https://m.bilibili.com/video/* // @run-at document-start // @grant none // @noframes // ==/UserScript== (function () { 'use strict'; /* ============ 配置(localStorage,键名 biliFastCfg) ============ */ var LS_KEY = 'biliFastCfg'; var DEFAULTS = { enabled: true, // 总开关 deferNonEssential: true, // 视频优先:延迟加载评论/推荐流/直播/空间等 deferDanmaku: true, // 弹幕列表同样延迟(视频开播后再加载) deferTimeout: 10000, // 兜底:最多延迟多久后开始加载其他(毫秒) releaseOnInteraction: true,// 用户滚动/点击时立即开始加载其他 priority: true, // 视频 CDN preconnect 预热 + 请求高优先级 showButton: true // 右下角悬浮开关按钮 }; function readCfg() { var cfg = {}; for (var k in DEFAULTS) cfg[k] = DEFAULTS[k]; try { var raw = localStorage.getItem(LS_KEY); if (raw) { var saved = JSON.parse(raw); for (var k2 in saved) if (k2 in DEFAULTS) cfg[k2] = saved[k2]; } } catch (e) {} return cfg; } var cfg = readCfg(); if (!cfg.enabled) return; var released = false; // 是否已开始加载"其他"内容 var pendingFetches = []; // 被暂缓的 fetch 调用(释放时按序发出) var pendingXhrs = []; // 被暂缓的 XHR 调用 /* ============ 判定:该请求是否应暂缓(而不是屏蔽) ============ */ function shouldDefer(raw) { if (released || !raw) return false; var url; try { url = new URL(String(raw), location.href); } catch (e) { return false; } var host = url.hostname; var path = url.pathname; // 视频流 CDN:永不延迟(这些是视频本身) if (/bilivideo\.(com|cn)$/.test(host) || /acgvideo\.com$/.test(host)) return false; // 直播 if (/\blive\.bilibili\.com$/.test(host) || /^api\.live\.bilibili\.com$/.test(host)) return true; // 弹幕列表(弹幕 WebSocket 不拦截,保持实时性) if (cfg.deferDanmaku && /^api\.bilibili\.com$/.test(host) && /\/x\/v[12]\/dm\//.test(path)) return true; // api.bilibili.com:白名单(播放/交互必需)之外的业务接口全部暂缓 if (/^api\.bilibili\.com$/.test(host)) { // 关闭弹幕延迟时,弹幕接口即使不在白名单也放行 if (!cfg.deferDanmaku && /\/x\/v[12]\/dm\//.test(path)) return false; var allow = [ /\/x\/web-interface\/(wbi\/)?view/, // 视频信息 /\/x\/web-interface\/(wbi\/)?nav/, // 登录状态 /\/x\/player\//, // 播放器配置/播放地址(playurl)/分P列表 /\/x\/report\//, // 播放心跳、进度上报(保存观看历史) /\/x\/web-interface\/archive\/stat/, // 播放/弹幕/点赞计数 /\/x\/web-interface\/like\//, // 点赞/三连 /\/x\/web-interface\/coin\//, // 投币 /\/x\/web-interface\/favorite\//, // 收藏 /\/x\/v2\/fav\//, // 稍后再看 /\/x\/web-interface\/archive\/add/, // 稍后再看 /\/x\/relation\// // 关注/粉丝 ]; for (var i = 0; i < allow.length; i++) { if (allow[i].test(path)) return false; } // 评论、相关推荐、底部推荐流、在线人数、作者空间、排行等 → 暂缓 return true; } return false; } /* ============ 释放:开始正常加载被暂缓的请求 ============ */ var origSend; function release() { if (released) return; released = true; // 取消"隐藏待加载区域"的样式类,评论区/右侧栏等恢复可见 try { document.documentElement.classList.remove('bili-defer'); } catch (e) {} // 按序发出被暂缓的 fetch var fs = pendingFetches.splice(0); for (var i = 0; i < fs.length; i++) { try { fs[i](); } catch (e) {} } // 按序发出被暂缓的 XHR(若页面已 abort 则跳过) var xs = pendingXhrs.splice(0); for (var j = 0; j < xs.length; j++) { try { if (typeof origSend === 'function' && xs[j].xhr.readyState === 1) { origSend.apply(xs[j].xhr, xs[j].args); } } catch (e) {} } } /* ============ fetch 暂缓 ============ */ var origFetch = window.fetch; if (typeof origFetch === 'function') { window.fetch = function (input, init) { var url = (typeof input === 'string' ? input : (input && input.url)) || ''; if (cfg.deferNonEssential && shouldDefer(url)) { var self = this; return new Promise(function (resolve, reject) { pendingFetches.push(function () { try { var p = origFetch.call(self, input, init); p.then(resolve, reject); } catch (e) { reject(e); } }); }); } // 视频 CDN 请求提高网络优先级(不支持 priority 选项的浏览器自动忽略) if (cfg.priority && init && init.priority == null && /bilivideo\.(com|cn)|acgvideo\.com/.test(url)) { try { init = Object.assign({}, init, { priority: 'high' }); } catch (e) {} } var p = origFetch.call(this, input, init); if (p && typeof p.then === 'function') { p.then(function (r) { sniffPlayurlHosts(url, r); }, function () {}); } return p; }; } /* ============ XHR 暂缓 ============ */ var XHR = window.XMLHttpRequest; if (XHR && XHR.prototype) { var origOpen = XHR.prototype.open; origSend = XHR.prototype.send; XHR.prototype.open = function (method, url) { try { this.__biliDefer = cfg.deferNonEssential && shouldDefer(url); } catch (e) { this.__biliDefer = false; } return origOpen.apply(this, arguments); }; XHR.prototype.send = function () { if (this.__biliDefer) { this.__biliDefer = false; pendingXhrs.push({ xhr: this, args: arguments }); return; // 暂不发出,等释放 } return origSend.apply(this, arguments); }; } /* ============ CDN preconnect 预热 ============ */ var preconnected = {}; function preconnect(host) { if (!host || preconnected[host]) return; preconnected[host] = 1; var head = document.head || document.documentElement; try { var pc = document.createElement('link'); pc.rel = 'preconnect'; pc.href = 'https://' + host; pc.crossOrigin = 'anonymous'; head.appendChild(pc); var dn = document.createElement('link'); dn.rel = 'dns-prefetch'; dn.href = 'https://' + host; head.appendChild(dn); } catch (e) {} } // 解析 playurl 响应,提前为真实视频 CDN 主机建立连接 function sniffPlayurlHosts(url, resp) { if (!cfg.priority || !resp || !resp.clone) return; try { resp.clone().text().then(function (t) { var m = t.match(/https?:\/\/[^"'\\\s]+/g) || []; for (var i = 0; i < m.length; i++) { try { var h = new URL(m[i]).hostname; if (/bilivideo\.(com|cn)$/.test(h) || /acgvideo\.com$/.test(h)) preconnect(h); } catch (e) {} } }).catch(function () {}); } catch (e) {} } /* ============ 视频元素优先级 ============ */ var boosted = (typeof WeakSet !== 'undefined') ? new WeakSet() : null; function boostVideo(v) { if (!v || (boosted && boosted.has(v))) return; try { if ('fetchPriority' in v) v.fetchPriority = 'high'; else v.setAttribute('fetchpriority', 'high'); if ('preload' in v) v.preload = 'auto'; if ('decoding' in v) v.decoding = 'async'; if (boosted) boosted.add(v); } catch (e) {} } /* ============ 释放触发:视频开播/缓冲充分/用户交互/兜底超时 ============ */ function onProgress() { try { var v = this; var b = v.buffered; if (b && b.length && v.duration) { var end = b.end(b.length - 1); // 已缓冲足够时长(60s 或时长一半)→ 视频无忧,开始加载其他 if (end >= Math.min(60, v.duration) || end >= v.duration * 0.5) release(); } } catch (e) {} } var watched = (typeof WeakSet !== 'undefined') ? new WeakSet() : null; function watchVideo(v) { if (!v) return; boostVideo(v); if (!cfg.deferNonEssential || (watched && watched.has(v))) return; try { watched.add(v); v.addEventListener('playing', release); // 开始播放 → 加载其他 v.addEventListener('canplay', release); // 缓冲到可播放 → 加载其他 v.addEventListener('error', release); // 视频出错 → 不耽误其他内容 v.addEventListener('progress', onProgress); } catch (e) {} } /* ============ 暂缓期间隐藏待加载区域(释放后自动恢复) ============ */ function injectDeferCss() { try { var st = document.createElement('style'); st.id = 'bili-fast-defer-css'; st.textContent = 'html.bili-defer .right-container,' + 'html.bili-defer #comment,' + 'html.bili-defer .comment-container,' + 'html.bili-defer .bili-comment,' + 'html.bili-defer .bili-footer,' + 'html.bili-defer #reco-wrap,' + 'html.bili-defer #reco_list,' + 'html.bili-defer #live_room,' + 'html.bili-defer .live-room { visibility:hidden !important; }'; (document.head || document.documentElement).appendChild(st); document.documentElement.classList.add('bili-defer'); } catch (e) {} } /* ============ 悬浮开关按钮 ============ */ function addToggle() { var btn = document.createElement('div'); btn.id = 'bili-fast-toggle'; btn.textContent = '⚡'; btn.title = 'B站视频优先加载:当前' + (cfg.deferNonEssential ? '开启' : '关闭') + ',点击切换并刷新页面'; btn.setAttribute('style', 'position:fixed;right:14px;bottom:90px;z-index:2147483647;width:34px;height:34px;line-height:32px;' + 'text-align:center;font-size:16px;border-radius:50%;cursor:pointer;user-select:none;' + 'background:' + (cfg.deferNonEssential ? 'rgba(0,161,214,.92)' : 'rgba(120,120,120,.92)') + ';' + 'color:#fff;box-shadow:0 2px 8px rgba(0,0,0,.25);font-family:system-ui,sans-serif;'); btn.addEventListener('click', function () { cfg.deferNonEssential = !cfg.deferNonEssential; try { localStorage.setItem(LS_KEY, JSON.stringify(cfg)); } catch (e) {} location.reload(); }); try { document.documentElement.appendChild(btn); } catch (e) {} } /* ============ 启动 ============ */ try { if (cfg.deferNonEssential) { injectDeferCss(); // 兜底超时:无论视频是否播放,最多延迟 deferTimeout 后开始加载其他 setTimeout(release, cfg.deferTimeout); // 用户滚动/点击(说明在浏览页面而非等视频)→ 立即开始加载其他 if (cfg.releaseOnInteraction) { ['scroll', 'wheel', 'click', 'keydown', 'touchstart'].forEach(function (t) { try { window.addEventListener(t, release, { capture: true, passive: true }); } catch (e) { try { window.addEventListener(t, release, true); } catch (e2) {} } }); } } if (cfg.priority) { preconnect('upos-sz-mirrorcos.bilivideo.com'); preconnect('upos-sz-mirror08.bilivideo.com'); preconnect('mcdn.bilivideo.cn'); } if (cfg.showButton) addToggle(); // 监听视频元素出现(SPA 切换视频也适用) if (window.MutationObserver) { var obs = new MutationObserver(function () { try { watchVideo(document.querySelector('video')); } catch (e) {} }); obs.observe(document.documentElement, { childList: true, subtree: true }); } watchVideo(document.querySelector('video')); window.__BILI_FAST__ = { cfg: cfg, LS_KEY: LS_KEY, shouldDefer: shouldDefer, release: release }; // 调试入口 } catch (e) {} })();